移动Web单页应用开发实践——页面结构化
1. 前言
在開發(fā)面向現(xiàn)代智能手機(jī)的移動Web應(yīng)用的時候,無法避免一個事實,就是需要開發(fā)單頁應(yīng)用(Single Page WebApp)。對于不同的系統(tǒng)需求,單頁應(yīng)用的粒度會不同,可能是整個系統(tǒng)都使用一個頁面裝載,也可能是按模塊分為獨立頁面裝載。在開發(fā)單頁應(yīng)用時第一個要處理的問題就是頁面結(jié)構(gòu)化,由于多個功能集中在一個頁面呈現(xiàn),就必然需要考慮如何實現(xiàn)多個視圖布局?如何實現(xiàn)視圖之間動畫切換?等問題。
下面我就來講述下手機(jī)搜狐前端團(tuán)隊在單頁應(yīng)用開發(fā)的頁面結(jié)構(gòu)化上做過的一些嘗試與努力。
2. 頁面視圖
在講頁面結(jié)構(gòu)化之前需要先理解視圖的概念,視圖是單頁應(yīng)用開發(fā)中最常見的模塊,通常在一個單頁應(yīng)用中,會有多個視圖存在,每一個視圖都可以處理一部分業(yè)務(wù)功能,所有視圖的功能集就是單頁應(yīng)用所能處理業(yè)務(wù)的最大能力。下面介紹幾種單頁應(yīng)用中最常出現(xiàn)的幾種視圖。
2.1 單視圖層
三段式結(jié)構(gòu)是單視圖的一種最基本布局方式,如下圖:
單視圖并不一定都有head或foot,所以Header、Footer使用虛線來表示。多數(shù)應(yīng)用中還會有導(dǎo)航條(Navigatior),但一般情況下導(dǎo)航條會被計算為Header或Content的一部分,而不會獨立存在。
2.2 側(cè)邊欄
側(cè)邊欄是一種特殊的視圖,在不顯示時,當(dāng)前視圖是蓋在側(cè)邊欄至上的,當(dāng)它被呼出時,視圖一部分滑出屏幕外,側(cè)邊欄才被顯示出來,它的高度等于頁面可視區(qū)域的高度。
顯示前:
顯示后:
2.3 封面圖
封面圖與側(cè)邊欄類似,也是一個特殊的視圖。封面圖一般會在頁面初始時候出現(xiàn),而后消失,消失之后就不再出現(xiàn)。它的視圖層級是最高的,并且完全覆蓋于其他頁面元素,它的高度會大于或等于可視區(qū)域的高度。
3 多視圖布局
單頁應(yīng)用中第一個要思考的問題就是:如何實現(xiàn)多視圖的布局?通常我們會將視圖的定位設(shè)置為position:absolute,這是一種簡單又實用的方法。在一個時間節(jié)點上,頁面可視區(qū)域只能有一個可見的當(dāng)前視圖,虛線表示其他視圖,在頁面可視區(qū)域之外不可見(display:none),如下圖:
使用偽代碼表示:
| <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> |
此時,我們需要思考另一個問題:如何實現(xiàn)當(dāng)前視圖的Content區(qū)域內(nèi)容滾動?視圖的樣式高度設(shè)置為height:100%,將視圖高度設(shè)定為一屏高的目的是為了方便實現(xiàn)視圖動畫切換的效果(視圖動畫切換會在后面詳細(xì)的講)。但這樣做會導(dǎo)致另一個問題,高度為一屏高意味著瀏覽器滾動條失效,無法使用瀏覽器滾動條滾動頁面。
3.1 基于iScroll的多視圖布局
現(xiàn)在比較流行的一種解決方案是使用iScroll組件實現(xiàn)固定區(qū)域滾動,這樣就能解決Content區(qū)域的滾動問題,在手機(jī)搜狐的早期項目也是這么做的。此外,使用iScroll還額外帶來了一些好處,如:
- Header區(qū)域能固定在頁面頂部,不會因為Content區(qū)域滾動導(dǎo)致Header被頂上去;
- 單視圖的高度控制在一屏高,這樣有利于實現(xiàn)視圖之間的動畫切換;
對于這種結(jié)構(gòu)的應(yīng)用,在使用視圖切換的時候就非常好做,使用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> |
視圖切換的動畫效果可以根據(jù)業(yè)務(wù)需求定制,比如:由左向右滑動、由右向左、由上到下、右下到上等都是可以的。在完成切換動畫時,再將next視圖的狀態(tài)設(shè)置為current,如下:
| <div class="view"></div> <div class="view current"></div> |
下圖是項目中使用的一個由下往上動畫切換效果:
3.2 iScroll頁面結(jié)構(gòu)下的側(cè)邊欄
使用iScroll的頁面結(jié)構(gòu),無論是側(cè)邊欄還是封面圖都非常好實現(xiàn),看偽代碼:
側(cè)邊欄,默認(rèn)狀態(tài)
| <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> |
側(cè)邊欄顯示時
| <div class="view sidebar show"></div> <div class="view current"></div> |
側(cè)邊欄隱藏時,當(dāng)hide動畫結(jié)束之后,移除hide樣式
| <div class="view sidebar hide"></div> <div class="view current"></div> |
3.3 iScroll頁面結(jié)構(gòu)下的封面圖
封面圖的實現(xiàn)與側(cè)邊欄差不多。
封面圖,默認(rèn)狀態(tài)
| <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> |
封面圖隱藏時,當(dāng)hide動畫結(jié)束之后,移除hide樣式
| <div class="view cover hide"></div> <div class="view current"></div> |
在項目中的實現(xiàn)效果:
3.4 iScroll對內(nèi)容刷新的支持
對于Content區(qū)域的內(nèi)容刷新iScroll也有很好的支持,可以直接參見iScroll提供的例子:http://lab.cubiq.org/iscroll/examples/pull-to-refresh/
Note:iScroll目前已經(jīng)更新到了5.0的版本,大家可以關(guān)注Github項目https://github.com/cubiq/iscroll/
4. 多視圖布局,新的探索
對于單頁應(yīng)用來說,iScroll確實是一個非常優(yōu)秀的解決方案,但是iScroll缺有一個最大的缺陷——慢,滾動的性能與瀏覽器原生實現(xiàn)相比,在低端的移動設(shè)備上有明顯卡頓,這點我在另一片博文中也提到過《移動Web產(chǎn)品前端開發(fā)口訣——“快”》。
Note:目前有一個新的趨勢,瀏覽器經(jīng)過一兩年的發(fā)展,Android下已經(jīng)優(yōu)化的相當(dāng)不錯,iScroll在一些較低端的移動設(shè)備上,性能表現(xiàn)得比以前要好非常多,比如小米1,早期的米1還在運行UC7.x的版本時,iScroll明顯的卡,現(xiàn)在在UC9.x下,iScroll也能運行得比較流暢了。
4.1 Fixed+原生Scroll
在此之下,我們也做了一些新的嘗試,第一嘗試就是放棄使用iScroll組件。放棄之后遇到的第一個問題,如何使Header固定位置在頂部?由此,我們使用了原生的CSS特性position:fixed,如下圖:
Fixed在一些移動設(shè)備瀏覽器上有兼容問題,我找到了一種能檢測瀏覽器是否支持position:fixed的方法,這個也發(fā)一篇博文《移動Web開發(fā),4行代碼檢測瀏覽器是否支持position:fixed》,在檢測到瀏覽器不支持fixed時,可以使用absolute作為替代方案,監(jiān)聽window的scroll事件,每次scroll動作結(jié)束時,重新計算一次Header的top值,將其定位到頁面頂部。
有關(guān)position:fixed的bug在另一篇博文中《移動端web頁面使用position:fixed問題總結(jié)》也有總結(jié)。
另外強(qiáng)調(diào)一點,不要在Fixed區(qū)域中直接使用input或textarea元素。在fixed元素中的input獲取焦點之后,彈出軟鍵盤會帶來很多額外的問題,如:
- 在iOS下軟鍵盤彈出,fixed定位會出問題;
- 在Android下軟件盤彈出,可能會導(dǎo)致輸入?yún)^(qū)域被遮擋;
點擊input彈出一個新視圖來完成后續(xù)輸入,是一種比較好的解決方案,下圖是一個基于iScroll的頁面結(jié)構(gòu)實現(xiàn):
4.2 原生Scroll下的視圖切換
使用了原生Scroll之后,帶來最大的改變是視圖切換動畫的變化。使用iScroll的頁面結(jié)構(gòu),視圖的高度固定,并且是position:absolute定位,所以非常容易做視圖切換。
換成原生Scroll之后,想使用一個比較緩和的動畫過渡效果是非常困難的,可選的動畫效果十分有限,經(jīng)過了很多試驗之后,最后選擇使用淡入-淡出的動畫效果,這是一種折中的方法。最初在完成這種動畫實現(xiàn)的時候,編碼的方法比較簡單,就是將當(dāng)前視圖淡出,下一視圖淡入,如下圖:
后來在做了更多嘗試之后,開發(fā)出了一種兼容更強(qiáng)的淡入-淡出動畫過渡。技術(shù)要點就是使用一個幕布層(mask)實現(xiàn)淡入效果,在mask完成淡入之后,再完成實際的視圖的切換,操作步驟大致如下:
- 創(chuàng)建一個幕布層<div class="mask"></div>,mask為position:absolute定位,初始為透明狀態(tài),背景設(shè)置為白色或其他顏色,并使mask蓋在當(dāng)前視圖上面;
- mask使用transition實現(xiàn)opacity:1的動畫過渡,當(dāng)完成動畫時,mask將會把當(dāng)前視圖完全遮住;
- 最后,直接將當(dāng)前視圖隱藏,將下一視圖顯示既可;
- 完成所有動作之后,隱藏mask;
效果圖:
4.3 原生Scroll頁面結(jié)構(gòu)下的側(cè)邊欄
側(cè)邊欄的結(jié)構(gòu)也變得復(fù)雜了一些,使用原生Scroll之后,body的高度會被內(nèi)容區(qū)域撐到很高,但側(cè)邊欄還是必須保證一屏高。所以我在側(cè)邊欄顯示時,將html與body的高度控制為一屏高,這樣可以防止頁面被滾動。使用偽代碼表示:
側(cè)邊欄,默認(rèn)狀態(tài)
| <html class="frame"> <head> <style type="text/css"> ????.frame { ????????height: 100%; ????} ????.sidebar { ???????? ????????position: absolute; ????????z-index: 50; ????????width: 80%; ????????height: 100%; ????} ????.scroller { ???????? ????????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> |
側(cè)邊欄顯示時,在html元素上增加一個樣式sidebar-show
| <html class="frame sidebar-show"> |
側(cè)邊欄隱藏時,將html元素上的樣式替換成sidebar-hide,當(dāng)hide動畫結(jié)束之后,移除hide樣式
| <html class="frame sidebar-hide"> |
在項目中的實際效果:
另外,將側(cè)邊欄設(shè)置為position:fixed定位會是另一種實現(xiàn)思路。
4.4 原生Scroll頁面結(jié)構(gòu)下的封面圖
封面圖的實現(xiàn)與側(cè)邊欄差不多,使用偽代碼表示:
封面圖,默認(rèn)為顯示狀態(tài)
| <html class="frame cover-show"> <head> <style type="text/css"> ????.frame, .frame body { ????????height: 100%; ????} ????.cover { ???????? ????????position: absolute; ????????z-index: 200; ????????width: 100%; ????????height: 100%; ????} ????.scroller { ???????? ????????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,當(dāng)hide動畫結(jié)束之后,移除hide樣式
| <html class="frame cover-hide"> |
項目中的應(yīng)用:
4.5 原生Scroll頁面結(jié)構(gòu)下,內(nèi)容刷新的實現(xiàn)
一般情況下,我們會頁面底部放一個加載更多的按鈕,讓用戶點擊按鈕加載下一頁內(nèi)容,如下圖:
又或者,監(jiān)聽window的scroll事件,當(dāng)頁面發(fā)生滾動時,監(jiān)測是否滾動到頁面底部,自動加載下一頁內(nèi)容。這兩種方式都能很好的解決加載下一頁的業(yè)務(wù)需求,但是對于加載最新或刷新的操作只能在頁面中放置一個刷新按鈕來完成業(yè)務(wù)需求。
對于Pull Up/Down Request的操作,在原生Scroll下,幾乎是無法實現(xiàn)的。但我依然希望能找到一種方法,實現(xiàn)Pull Request操作。
現(xiàn)在我正在研究一種模擬Pull操作的解決方案,已經(jīng)有了一個雛形,并實現(xiàn)了一些功能。下面這個示例中沒有使用任何的iScroll技術(shù),完全使用原生Scroll實現(xiàn)頁面滾動,并且滾動到頁面底部后可以完成Pull Up操作,如下圖:
這個技術(shù)的實現(xiàn)原理并不復(fù)雜,就是在頁面滾動到底部時,創(chuàng)建一個空白層,模擬Pull Up手勢拖動頁面的效果。
我后面會封裝成一個組件放在GitHub上分享給大家。
5 結(jié)束語
手機(jī)搜狐目前還是一個年輕的前端團(tuán)隊,在手機(jī)搜狐的一年半時間,積累和很多有關(guān)移動端Web開發(fā)的經(jīng)驗,寫這篇文章希望能將自己在移動Web方面的一些經(jīng)驗分享給大家,同時,也希望能有更多的移動Web開發(fā)者能互相交流。
https://github.com/maxzhang/maxzhang.github.com/issues/8
轉(zhuǎn)載于:https://www.cnblogs.com/huhl/p/3962102.html
總結(jié)
以上是生活随笔為你收集整理的移动Web单页应用开发实践——页面结构化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 下一代Asp.net开发规范OWIN(2
- 下一篇: 博客园上看到边边的时钟,觉得很好看!推荐