在開發面向現代智能手機的移動Web應用的時候,沒法避免一個事實,就是須要開發單頁應用(Single Page WebApp)。對於不一樣的系統需求,單頁應用的粒度會不一樣,多是整個系統都使用一個頁面裝載,也多是按模塊分爲獨立頁面裝載。在開發單頁應用時第一個要處理的問題就是頁面結構化,因爲多個功能集中在一個頁面呈現,就必然須要考慮如何實現多個視圖佈局?如何實現視圖之間動畫切換?等問題。css
下面我就來說述下手機搜狐前端團隊在單頁應用開發的頁面結構化上作過的一些嘗試與努力。html
在講頁面結構化以前須要先理解視圖的概念,視圖是單頁應用開發中最多見的模塊,一般在一個單頁應用中,會有多個視圖存在,每個視圖均可以處理一部分業務功能,全部視圖的功能集就是單頁應用所能處理業務的最大能力。下面介紹幾種單頁應用中最常出現的幾種視圖。前端
三段式結構是單視圖的一種最基本佈局方式,以下圖:git
單視圖並不必定都有head或foot,因此Header、Footer使用虛線來表示。多數應用中還會有導航條(Navigatior),但通常狀況下導航條會被計算爲Header或Content的一部分,而不會獨立存在。github
側邊欄是一種特殊的視圖,在不顯示時,當前視圖是蓋在側邊欄至上的,當它被呼出時,視圖一部分滑出屏幕外,側邊欄才被顯示出來,它的高度等於頁面可視區域的高度。web
顯示前:瀏覽器
顯示後:ruby
封面圖與側邊欄相似,也是一個特殊的視圖。封面圖通常會在頁面初始時候出現,然後消失,消失以後就再也不出現。它的視圖層級是最高的,而且徹底覆蓋於其餘頁面元素,它的高度會大於或等於可視區域的高度。移動端web
單頁應用中第一個要思考的問題就是:如何實現多視圖的佈局?一般咱們會將視圖的定位設置爲position:absolute
,這是一種簡單又實用的方法。在一個時間節點上,頁面可視區域只能有一個可見的當前視圖,虛線表示其餘視圖,在頁面可視區域以外不可見(display:none
),以下圖:ide
使用僞代碼表示:
<style type="text/css"> .view { position: absolute; top: 0; left: 0; z-index: 99; display: none; width: 100%; height: 100%; } .current { z-index: 100; display: block; } </style> <div class="view current"></div> <div class="view"></div>
此時,咱們須要思考另外一個問題:如何實現當前視圖的Content區域內容滾動?視圖的樣式高度設置爲height:100%
,將視圖高度設定爲一屏高的目的是爲了方便實現視圖動畫切換的效果(視圖動畫切換會在後面詳細的講)。但這樣作會致使另外一個問題,高度爲一屏高意味着瀏覽器滾動條失效,沒法使用瀏覽器滾動條滾動頁面。
如今比較流行的一種解決方案是使用iScroll組件實現固定區域滾動,這樣就能解決Content區域的滾動問題,在手機搜狐的早期項目也是這麼作的。此外,使用iScroll還額外帶來了一些好處,如:
對於這種結構的應用,在使用視圖切換的時候就很是好作,使用CSS3的transition來完成動畫切換,以下圖:
使用僞代碼表示:
<style type="text/css"> .current.out { -webkit-transition: -webkit-transform 400ms; -webkit-transform: translate3d(-100%,0,0); } .next { display: block; -webkit-transform: translate3d(100%,0,0); } .next.in{ -webkit-transition: -webkit-transform 400ms; -webkit-transform: translate3d(0,0,0); } </style> <div class="view current out"></div> <div class="view next in"></div>
視圖切換的動畫效果能夠根據業務需求定製,好比:由左向右滑動、由右向左、由上到下、右下到上等都是能夠的。在完成切換動畫時,再將next視圖的狀態設置爲current,以下:
<div class="view"></div> <div class="view current"></div>
下圖是項目中使用的一個由下往上動畫切換效果:
使用iScroll的頁面結構,不管是側邊欄仍是封面圖都很是好實現,看僞代碼:
側邊欄,默認狀態
<style type="text/css"> .sidebar { z-index: 50; display: block; width: 80%; } .sidebar.show + .current { -webkit-transition: -webkit-transform 400ms; -webkit-transform: translate3d(80%,0,0); } .sidebar.hide + .current { -webkit-transition: -webkit-transform 400ms; -webkit-transform: translate3d(0,0,0); } </style> <div class="view sidebar"></div> <div class="view current"></div>
側邊欄顯示時
<div class="view sidebar show"></div> <div class="view current"></div>
側邊欄隱藏時,當hide動畫結束以後,移除hide樣式
<div class="view sidebar hide"></div> <div class="view current"></div>
封面圖的實現與側邊欄差很少。
封面圖,默認狀態
<style type="text/css"> .cover { z-index: 200; display: block; visibility: hidden; opacity: 0; } .cover.show { visibility: visible; -webkit-transition: opacity 400ms; opacity: 1; } .cover.hide { visibility: visible; -webkit-transition: opacity 400ms; opacity: 0; } </style> <div class="view cover"></div> <div class="view current"></div>
封面圖顯示時
<div class="view cover show"></div> <div class="view current"></div>
封面圖隱藏時,當hide動畫結束以後,移除hide樣式
<div class="view cover hide"></div> <div class="view current"></div>
在項目中的實現效果:
對於Content區域的內容刷新iScroll也有很好的支持,能夠直接參見iScroll提供的例子:http://lab.cubiq.org/iscroll/examples/pull-to-refresh/
Note:iScroll目前已經更新到了5.0的版本,你們能夠關注Github項目https://github.com/cubiq/iscroll/
對於單頁應用來講,iScroll確實是一個很是優秀的解決方案,可是iScroll缺有一個最大的缺陷——慢,滾動的性能與瀏覽器原生實現相比,在低端的移動設備上有明顯卡頓,這點我在另外一片博文中也提到過《移動Web產品前端開發口訣——「快」》。
Note:目前有一個新的趨勢,瀏覽器通過一兩年的發展,Android下已經優化的至關不錯,iScroll在一些較低端的移動設備上,性能表現得比之前要好很是多,好比小米1,早期的米1還在運行UC7.x的版本時,iScroll明顯的卡,如今在UC9.x下,iScroll也能運行得比較流暢了。
在此之下,咱們也作了一些新的嘗試,第一嘗試就是放棄使用iScroll組件。放棄以後遇到的第一個問題,如何使Header固定位置在頂部?由此,咱們使用了原生的CSS特性position:fixed
,以下圖:
Fixed在一些移動設備瀏覽器上有兼容問題,我找到了一種能檢測瀏覽器是否支持position:fixed的方法,這個也發一篇博文《移動Web開發,4行代碼檢測瀏覽器是否支持position:fixed》,在檢測到瀏覽器不支持fixed時,可使用absolute做爲替代方案,監聽window的scroll事件,每次scroll動做結束時,從新計算一次Header的top值,將其定位到頁面頂部。
有關position:fixed
的bug在另外一篇博文中《移動端web頁面使用position:fixed問題總結》也有總結。
另外強調一點,不要在Fixed區域中直接使用input或textarea元素。在fixed元素中的input獲取焦點以後,彈出軟鍵盤會帶來不少額外的問題,如:
點擊input彈出一個新視圖來完成後續輸入,是一種比較好的解決方案,下圖是一個基於iScroll的頁面結構實現:
使用了原生Scroll以後,帶來最大的改變是視圖切換動畫的變化。使用iScroll的頁面結構,視圖的高度固定,而且是position:absolute
定位,因此很是容易作視圖切換。
換成原生Scroll以後,想使用一個比較緩和的動畫過渡效果是很是困難的,可選的動畫效果十分有限,通過了不少試驗以後,最後選擇使用淡入-淡出的動畫效果,這是一種折中的方法。最初在完成這種動畫實現的時候,編碼的方法比較簡單,就是將當前視圖淡出,下一視圖淡入,以下圖:
後來在作了更多嘗試以後,開發出了一種兼容更強的淡入-淡出動畫過渡。技術要點就是使用一個幕布層(mask)實現淡入效果,在mask完成淡入以後,再完成實際的視圖的切換,操做步驟大體以下:
<div class="mask"></div>
,mask爲position:absolute
定位,初始爲透明狀態,背景設置爲白色或其餘顏色,並使mask蓋在當前視圖上面;opacity:1
的動畫過渡,當完成動畫時,mask將會把當前視圖徹底遮住;效果圖:
側邊欄的結構也變得複雜了一些,使用原生Scroll以後,body的高度會被內容區域撐到很高,但側邊欄仍是必須保證一屏高。因此我在側邊欄顯示時,將html與body的高度控制爲一屏高,這樣能夠防止頁面被滾動。使用僞代碼表示:
側邊欄,默認狀態
<html class="frame"> <head> <style type="text/css"> .frame { height: 100%; } .sidebar { background-color: red; position: absolute; z-index: 50; width: 80%; height: 100%; } .scroller { background-color: green; position: relative; z-index: 100; height: 2000px; } .sidebar-show body, .sidebar-hide body { height: 100%; } .sidebar-show .scroller { overflow: hidden; height: 100%; -webkit-transition: -webkit-transform 400ms; -webkit-transform: translate3d(80%,0,0); } .sidebar-hide .scroller { overflow: hidden; height: 100%; -webkit-transition: -webkit-transform 400ms; -webkit-transform: translate3d(0,0,0); } </style> </head> <body> <div class="sidebar"></div> <div class="scroller"></div> </body> </html>
側邊欄顯示時,在html元素上增長一個樣式sidebar-show
<html class="frame sidebar-show">
側邊欄隱藏時,將html元素上的樣式替換成sidebar-hide,當hide動畫結束以後,移除hide樣式
<html class="frame sidebar-hide">
在項目中的實際效果:
另外,將側邊欄設置爲position:fixed
定位會是另外一種實現思路。
封面圖的實現與側邊欄差很少,使用僞代碼表示:
封面圖,默認爲顯示狀態
<html class="frame cover-show"> <head> <style type="text/css"> .frame, .frame body { height: 100%; } .cover { background-color: red; position: absolute; z-index: 200; width: 100%; height: 100%; } .scroller { background-color: green; position: relative; z-index: 100; height: 2000px; } .cover-show body, .cover-hide body { height: 100%; } .cover-show .scroller { overflow: hidden; height: 100%; } .cover-hide .cover { -webkit-transition: opacity 400ms; opacity: 0; } </style> </head> <body> <div class="cover"></div> <div class="scroller"></div> </body> </html>
封面圖隱藏時,將html元素上的樣式替換成cover-hide,當hide動畫結束以後,移除hide樣式
<html class="frame cover-hide">
項目中的應用:
通常狀況下,咱們會頁面底部放一個加載更多的按鈕,讓用戶點擊按鈕加載下一頁內容,以下圖:
又或者,監聽window的scroll事件,當頁面發生滾動時,監測是否滾動到頁面底部,自動加載下一頁內容。這兩種方式都能很好的解決加載下一頁的業務需求,可是對於加載最新或刷新的操做只能在頁面中放置一個刷新按鈕來完成業務需求。
對於Pull Up/Down Request的操做,在原生Scroll下,幾乎是沒法實現的。但我依然但願能找到一種方法,實現Pull Request操做。
如今我正在研究一種模擬Pull操做的解決方案,已經有了一個雛形,並實現了一些功能。下面這個示例中沒有使用任何的iScroll技術,徹底使用原生Scroll實現頁面滾動,而且滾動到頁面底部後能夠完成Pull Up操做,以下圖:
這個技術的實現原理並不複雜,就是在頁面滾動到底部時,建立一個空白層,模擬Pull Up手勢拖動頁面的效果。
我後面會封裝成一個組件放在GitHub上分享給你們。
手機搜狐目前仍是一個年輕的前端團隊,在手機搜狐的一年半時間,積累和不少有關移動端Web開發的經驗,寫這篇文章但願能將本身在移動Web方面的一些經驗分享給你們,同時,也但願能有更多的移動Web開發者能互相交流。