最近幾年,隨着先後端分離、單頁面應用的崛起,網頁正變得愈來愈應用化。移動互聯網端的發展更是滋長了這個趨勢——對於交互、性能不敏感的場景,Web App在開發成本、跨平臺兼容上有着明顯優點。html
但在這火爆的行情背後,不少時候從產品經理到設計甚至開發,對Web平臺的特性並無足夠的瞭解與警覺,致使最終產品成了既不App也不Web的四不像,不只拖累用戶體驗,開發團隊也容易無所適用。前端
這篇文章但願圍繞着Web的特性,探討Web App與Native App的不一樣,幫助讀者在項目中儘早地識別出可能出現問題的場景。html5
Web App 也要按照基本法git
Web App或者單頁面應用(Single Page Application),名字裏帶個App,聽起來很有鳥槍換炮的味道,不管是工程師仍是利益關係人(產品經理、客戶),不免會有和尚摸得我摸不得,App作得我作不得?
的錯覺。更有甚者,把原生App的設計稿P上個瀏覽器地址欄就丟給團隊,讓照着作個Web App出來。若是這是你遇到的狀況,那麼恭喜,你已經在被坑的路上了程序員
俗話說入鄉隨俗,既然運行在瀏覽器上,即便Web App能很大程度上提供接近App的體驗,但有幾點,是從根本上不一樣於App而又必須考慮的:github
App對用戶的訪問路徑擁有近乎絕對的控制權:用戶從哪些地方進入應用(圖標、通知欄、分享連接)有限且可預測。而每一頁提供的操做入口之和,都是用戶當前可訪問的子路徑的全集。後端
而Web對用戶的訪問路徑控制權近乎爲0:任何頁面的URL都是對用戶可見的,用戶能夠添加書籤,分享給親友——任何頁面均可能被直接訪問,必須在設計和架構層面考慮。瀏覽器
鑑權和被訪問並不矛盾:本文中被訪問嚴格來說是被用戶請求,而鑑權或重定向則是對請求的處理服務器
這三個操做是全部Web瀏覽器工具欄的標準配置,而App多數狀況下只須要考慮返回操做。前端工程師
最麻煩是刷新:它會清空頁面內存、從新發起請求、從新執行腳本。這意味着Web端跨頁面的內存數據是不可靠的,若是A頁面依賴了B頁面放在內存中的數據,下次刷新時獲得的只會是undefined
。
用戶可能在應用或站點上進行一系列的、跨頁面的操做,這些用戶行爲稱爲上下文
,而操做產生的數據(好比用戶選擇或表單輸入),本文接下來稱做上下文數據
。
因爲App對路徑擁有絕對的控制權且不考慮刷新,某種程度上說App打開新的視圖和在網頁上打開模態窗的成本是同樣的,下游頁面能夠假定上游頁面生成的內存數據必定存在,而這些數據徹底經過代碼存取,對數據結構沒有限制,所以App端的上下文數據是複雜、可依賴的。
反觀Web,因爲每一個頁面均可以被直接訪問、刷新、甚至是複製連接到另外一臺設備上訪問,存儲在內存、LocalStorage中的數據都所以變得不可依賴,惟一能可靠傳遞的上下文信息僅限於URL能夠表徵的數據,在沒有服務器幫助的狀況下,Web端的上下文數據是簡單或不可依賴的。
現代移動端瀏覽器提供了一種應用模式,能夠屏蔽瀏覽器工具欄,讓頁面更像原生App。然而現階段真實應用的案例並很少見,而且至少有兩個問題:
後退沒法徹底屏蔽:安卓平臺的系統
返回
鍵等同於瀏覽器的後退
。移動端隨時吃緊的內存可能致使當前不活動的頁面被內存回收,下次打開時從新加載——至關於一次刷新。
上面三點其實都圍繞着核心——URL,某種程度上說,整個Web世界都是被URL標識並驅動的,每一個URL都應該定位到相應的資源
而現實的狀況是:可能你在作一個面向消費者(如下稱2C
)的項目,但需求方壓根就沒提URL和刷新的事;也可能你作的只是一個後臺管理系統,不會有人在意這東西,甚至會有一些和URL的定位特性相悖的需求。
這是最可怕的事,也是我寫這篇文章的初衷:在真正遇到麻煩前,不多有人(甚至包括資深的前端工程師)會全面的思考這類問題,等掉到坑裏的時候才發現積重難返。
在不假思索地接受這些設定前,但願你能仔細思考如下幾個問題:
客戶對URL與Web體驗間的關係有無概念?
客戶是否能把瀏覽器工具欄操做與真實的業務場景聯繫起來?
在項目初期,詢問客戶對刷新的見解時,客戶明確表示全部頁面,刷新一概回首頁
,由於客戶所參考的另外一個競品網站就是這麼作的。做爲技術團隊,很容易就此得出沒必要兼容刷新
的結論。
然而隨着瞭解的深刻,咱們發現客戶有以下需求:
支持郵件推銷:在發給用戶的郵件裏會附帶某個產品的連接,打開連接但願用戶能看到相關產品頁面
支持某些頁面保存連接:在訂單完成後,會爲用戶生成一個含有二維碼的憑證頁做爲取貨憑證。用戶能夠保存這個頁面的連接,並在取貨時(多是幾天後)直接打開。
以上兩個需求,從技術角度看和支持刷新是等價的:都要求能僅經過URL定位並展現頁面資源。
然而先後溝通的結果卻徹底不一樣,若是技術團隊按照最開始的結果作架構,到最後必定會付出代價。
回過頭看,一開始客戶給出的答案並不是剛性需求
,背後沒有業務價值,只是由於看到別人這麼作而已,甚至客戶還覺得本身主動削減了需求,爲開發團隊省了事。而這樣的需求,後期改變的可能性和彈性都是很是大的。
後面兩條,則是包含業務價值的剛性需求
,直接影響到客戶利益,幾乎沒有任何討價還價的餘地。
要避免這類問題,最重要的是警戒涉及技術架構的非剛性需求
,從多種角度進行確認,引導並挖掘出更多的相關場景。客戶是純業務的,對不一樣業務場景間的技術共性沒有概念。而如何引導客戶、挖掘真實需求,則是團隊專業性最直接的體現。
在上面這個案例中,當客戶說」不用考慮刷新,一概回首頁「的時候,團隊只要多問一句」那有沒有須要直接用URL打開的頁面呢?「,就極可能引導出更真實的需求。客戶並不知道二者在技術上等價,開發團隊這時候須要承擔起引導的責任。
一個典型的模態窗(Modal)以下圖所示:
它經過模仿窗口的方式,容許用戶在不脫離當前頁面上下文的狀況下進行操做。同時它也有必定的阻塞性,可用來控制用戶的操做流程,好比有的電商網站會經過模態窗來引導用戶先選擇所在地區再繼續購物。
以前咱們講過Web頁面間的上下文是強隔離的,而模態窗在不脫離上下文的前提下能承載的內容很是可觀,可觀到什麼承度呢?若是把模態窗的長寬設置爲填滿當前頁面,那它看起來會和新的頁面沒什麼兩樣!至關於額外100%的內容承載力!
這既提供了設計上的靈活性,也潛藏着風險:既然模態窗能夠承載和新頁面同樣多的內容,爲何不直接用它代替頁面呢?和跳轉到新頁面相比,它還能夠保持當前頁的全部狀態:表單內容、滾動位置……
問題在於,若是須要用URL定位到模態窗中的內容,渲染的順序必定是先有模態依附的頁面主體,再有模態窗。URL不只須要攜帶模態窗的相關信息,連頁面主體的也得帶上。要是模態套模態,套得越深,還原頁面的所需信息越複雜,離URL友好也就越遠。
這就像平房和樓房,雖然樓房利用了縱向空間,但想進門必定要從底樓一層層上去。空中樓閣是不存在的,模態窗也不可能脫離本身依附的主體獨立存在。
案例:某後臺管理系統
在我剛畢業的時候接觸過一個後臺管理系統,因爲是小公司,徹底沒有Web設計的相關經驗。
那個系統的設計幾乎是徹底忽略URL的,一開始是翻頁等重要信息沒有體如今URL中,致使資源沒法定位,一旦脫離某個模塊,則在該模塊下的全部操做都得重來。
爲了解決這個問題,設計(實際上是非設計出身的領導)給出的方案是用模態窗——既然找回上下文困難,不脫離上下文不就好了。
然而這時候的模態就像毒品:想要查看設備的子設備列表?開個模態窗;想看某個子設備的運行情況?再開個模態窗。就這樣模態窗套模態窗,有礙觀瞻是小事,當某天你意識到Web的世界還有URL這回事的時候,或者客戶報怨說想經過打開保存的連接就能看到某個子設備的時候,要改回來已經幾乎不可能了。
要避免相似的問題,至少須要在設計階段(或開發review設計時)注意如下兩點:
模態承載的內容和功能應儘可能單一。若是是但願用URL定位的資源,儘可能不要設計到模態窗中
例如登陸窗、或者展現商家地圖位置,都是適合模態窗的場景。而像訂單這樣的資源,因爲資源間關係複雜,極可能再出現顯示子資源或關聯資源的需求,用模態窗展現的話未來就很容易嵌套。
有種特殊狀況是資源自己有URL友好的頁面顯示,爲了用戶體驗在其它地方也用模態窗展現該資源的信息,這樣是沒有問題的,由於模態窗並不須要被URL定位
不想脫離當面頁面又想訪問其它資源?其實Web早就想到了,這就是你天天都在使用的新標籤頁打開連接
好比訂單列表頁,每一個訂單要有訂單詳情的功能入口,而用戶多是經過複雜的搜索條件以及漫長的鼠標滾動纔看到當前的列表項,並不但願脫離當前頁面。把訂單詳情放模態?缺點前面已經講過了,更好的方式是在新窗口中打開訂單詳情頁,用戶不只不會脫離當前頁面,甚至能夠同時打開多個子頁面,比不能併發的模態高到不知道哪裏去了。
若是說內容超載的問題主要發生在桌面端,那在移動端還有另外一個涉及模態窗的虛胖
問題,我的稱這種設計爲僞頁面
,如圖所示:
第一個頁面是主頁,點擊箭頭所示的日期會打開選擇日期的模態窗。你能夠親自上攜程移動端查看並操做。
相似設計移動端很是常見:因爲屏幕過小,有些功能本質雖然單一(如城市選擇、日期選擇,本質上都是個選擇框),體如今UI上仍然須要佔滿全屏。但這些功能(特別是表單類操做)又絕對不能脫離上下文存在,最終只能選擇用模態窗來處理。
這就產生了矛盾:全屏顯示的功能很容易給用戶「這是一個頁面」的錯覺,而實際上它並非。這個矛盾在面對刷新
、後退
、前進
等操做時尤爲明顯:
當模態窗打開的時候,點擊刷新,要不要從新打開模態窗?
當模態窗打開的時候,瀏覽器後退是關閉模態窗仍是後退到真實的上一頁?
若是後退能夠關閉模態窗,那前進是否能夠打開它?
感興趣的朋友不妨在剛纔攜程的連接上將以上幾點操做一番,親身感覺。
這些細節是需求分析和設計階段很難考慮全面的,每每遇到問題了纔開始頭痛醫頭腳痛醫腳,最終不管對用戶體驗仍是技術團隊,都很容易形成傷害。
我的對此並無十全十美的解決方案:全屏顯示的需求與功能並不是頁面的本質,這二者都沒法輕易解決
。假如本文能讓你在正式開發前就想到這個問題,對我來講已是功德一件了。
不過也並不是徹底沒有辦法,既佔滿屏幕,又能暗示背景上下文的設計從iOS 7就開始流行了——沒錯,就是毛玻璃效果
粗略地改了一下攜程的日期選擇界面樣式,以下圖所示:
經過毛玻璃效果消除了彈出層是新頁面的錯覺後,用戶就不容易對刷新
、後退
等操做出現錯誤的指望。
要作到URL友好,一個必要條件就是頁面能自行加載全部須要的外部資源。
什麼是外部資源?不就是服務器端數據嗎?對,可是不全對。
隨着接口的無狀態化,Web App會保存一些本來在服務器端存在的狀態,例如登陸信息。對每一個頁面而言,這些跨頁面共享的狀態也應該視做頁面的外部資源
。
這聽起來有點奇怪:頁面就是Web App的一部分,卻要把Web App的狀態視做外部資源
?
其實很好理解,核心仍然在於:組成Web App的每一個頁面,其上下文是隔離的。
還記得文章開頭的基本差別第三條吧?以用戶登陸信息爲例,即便你把信息放進了localStorage以保證刷新後可訪問,也擋不住用戶在其它瀏覽器訪問同一個連接,甚至乾脆把連接分享給別人——這意味你不能假定這些數據的可靠性,對單個頁面而言,Web App的狀態與服務器資源同樣是不可知的黑盒。
所以,即便你能夠像訪問內存同樣用同步代碼訪問localStorage,我仍然建議你把Web App的狀態管理與API請求放到相同的層級進行考量,這樣能更好地反映對問題本質的抽象。
插播技術觀點一則:localStorage規範同步IO更像是歷史遺留問題。將來必定會被異步存取的標準或第三方庫取代(火狐就搞了一個異步的localforage),將應用狀態與API同等對待的向前兼容性更優。
這個問題看起來沒什麼好說的——固然是在頁面加載時。
但若是需求方給你的是App的設計稿,那請務必注意:App的設計有多是在上個頁面加載下一頁的數據,再根據結果決定是否跳轉到下個頁面;而Web因爲URL的獨立可訪問性,一般是先跳轉到目標頁面再加載數據
這帶來的第一個差別就是Loading進度的顯示問題:App能夠在上一個頁面顯示Loading,而Web則不建議這麼作。緣由很簡單,頁面本身能加載資源,上個頁面再幫它加載,結果必定是要麼浪費資源,要麼增長複雜度。
目前多數App的設計和Web同樣是先跳轉再加載,然而先加載再跳轉的設計也是存在的,好比下圖國外某航空公司App:
點擊「Find flights」,會出現如圖所示的小菊花,等到加載結束才跳離當前頁面。
而攜程移動版(不管Web仍是App)就是標準的頁面自行顯示loading:
第二個差別則是如何處理加載失敗。在App端,若是由上一頁面加載下個頁面的數據,則錯誤的處理(例如彈窗)也會在上個頁面顯示,下游頁面從設計上基本不會考慮加載失敗時的渲染問題。
上圖是點擊"Find flights"請求失敗後,頁面沒有跳轉,直接在當前頁提示錯誤。
做爲對比,攜程的移動端如圖:
從以上截圖也能夠看出來,國外航空App的設計若是作成網站,是很難保證URL友好的。稍微總結一下的話就是:
Web因爲URL的獨立可訪問,每一個頁面都必須考慮加載中、加載失敗的時候如何顯示,若是設計給你的只有成功的場景,請立刻找他從新覈對需求。
苟利老闆虧盈以,豈因禍福避趨之
工程即妥協,有時候爲了更好的用戶體驗,上述任何一點均可能犧牲。本文的目的不是造成教條,而是幫助你在權衡利弊時能想得更加清楚。
更況且,不管是Web仍是App,都在高速發展並相互影響。瀏覽器賦予開發者更加豐富的、接近App的接口,而App也在借鑑Web的資源定位機制。
另外一方面,本文所討論的實際上是處於設計與技術間的灰色地帶,甚至須要工程師去挑戰和影響設計。這是由於團隊分工程度越高,這些灰色地帶的銜接就越可能致使項目失敗。我的很是鼓勵程序員,特別是前端工程師去思考業務和設計,乃至於輸出影響力。前端開發的本質是data -> design
的函數,只有深刻地理解輸入與輸出,甚至主動出擊修正誤差,才能交付真正的價值。