前言
web早已經進入了2.0時代了,如今的網頁大有往系統應用級別的方向發展的趨勢,再也不是以前的簡單展示資訊的介面了。如今很多webapp已經做到了原生應用的功能,並且運用自身的優勢逐步取代之。HTML5也很給力,對多平臺,多螢幕裝置的良好相容性使得前端工程師們在各種平臺上大顯身手。滷煮兩年前進公司接到的也是一個SPA應用的專案,也頗有些自己的心得,今日就寫篇博文,與大家分享下。
SPA
單頁 Web 應用 (single-page application 簡稱為 SPA) 是一種特殊的 Web 應用。它將所有的活動侷限於一個Web頁面中,僅在該Web頁面初始化時載入相應的HTML、JavaScript 和 CSS。一旦頁面載入完成了,SPA不會因為使用者的操作而進行頁面的重新載入或跳轉。取而代之的是利用 JavaScript 動態的變換HTML的內容,從而實現UI與使用者的互動。由於避免了頁面的重新載入,SPA 可以提供較為流暢的使用者體驗。得益於ajax,我們可以實現無跳轉重新整理,又多虧了瀏覽器的histroy機制,我們用hash的變化從而可以實現推動介面變化。從而模擬元素客戶端的單頁面切換效果:
優缺點
SPA被人追捧是有道理的,但是它也有不足之處。當然任何東西都有兩面性,以下是滷煮總結的一些目前SPA的優缺點:
優點:
1.無重新整理介面,給使用者體驗原生的應用感覺
2.節省原生(android和ios)app開發成本
3.提高發布效率,無需每次安裝更新包。這個對於ios開發人員來說印象尤其深吧。
4.容易藉助其他知名平臺更有利於營銷和推廣
5.符合web2.0的趨勢
缺點:
1.效果和效能確實和原生的有較大差距
2.各個瀏覽器的版本相容性不一樣
3.業務隨著程式碼量增加而增加,不利於首屏優化
4.某些平臺對hash有偏見,有些甚至不支援pushstate。(你懂的)
5.不利於搜尋引擎抓取
框架
一種開發模式火起來之後,對應的框架會隨之而起。像近幾年比較火的angularJS,是目前中最流行的mvvm框架,非常適合做SPA;與之類似的還有vueJS,滷煮在專案中也用過,相對於前者比較輕量。還有早一些的backbone,提供最基本的mvc模式,其他的模組大小和細節得自己決定。最早接觸的應該是extjs吧,這頭超級巨無霸在很早的時候被用來建立企業後臺應用,如今也跟著時代的變化做出了很多的改進。等等這些框架也好,庫也好,都旨在為了更好的構建SPA應用而生的,它們優缺點滷煮就不在此一一提了。
hash值
在此處提到一個比較重要的概念:URL中的井號。其實它只是瀏覽地址中的一個特殊符號。在以前,我們經常用它來定位文件位置。例如以下程式碼:
1 2 3 |
<a href="target">go target</a> ...... <div id="target">i am target place</div> |
點選a連結,文件會滾動到id為target的div的可視區域上面去。hash除了這個功能還有另一一種含義:指導瀏覽器的行為但不上傳到伺服器。大家都知道,改變url中的任何一個字元都會導致瀏覽器重新請求伺服器,除了#號後面那段字元之外。所以,簡而言之我們可以這樣理解:改變#後面的值不觸發網頁過載,但會記錄到瀏覽器history中去。
實現原理
實現SPA的方法有很多,終歸一種遵循一種原則,介面無重新整理。如果要實現原生應用中類似許多不同頁面切換的效果,我們採用的是div切換顯示和隱藏。而驅動div顯示隱藏的方式有很多種
1.監聽位址列中hash變化驅動介面變化
2.用pushsate記錄瀏覽器的歷史,驅動介面傳送變化
3.直接在介面用普通事件驅動介面變化
前兩種方式較為普遍,因為它們的變化記錄瀏覽器會儲存在history中,可以通過回退/前進按鈕找回,或者history物件中的方法控制。最後一種方法是用普通事件驅動的,沒有改變瀏覽器的history物件,所以一旦使用者按了返回按鈕將會退到瀏覽器的主介面。所以,一般採用前兩種方式。值得一提的是,在不支援hash監聽和pushsate變化的瀏覽器中可以考慮用延時函式,不停得去監聽瀏覽器位址列中url發生的變化,從而驅動介面變化。
從零開始
滷煮在很久之前的一篇博文用pushstate的變化做了一個小小的示例,大家可以在之前的博文中找到它。在這裡,我們用監聽hash變化的方式展示SPA是怎麼樣執行工作的,同時從零開始,搭建一個基礎的SPA。幫助大家理解簡單的單介面應用的原理。
首先,我們畫出三個div,它們實際上是作為三個介面存在介面上的,body作為介面外框容器,限制著它們的大小。為了給每個介面配對一個hash地址,我們給每個div配一個id,講hash地址與對應的選擇器(id、class)建立連結關係,從而可以從hash變化值中操作介面。
1 2 3 4 5 |
<body> <div id="A" class="a J-A">A</div> <div id="B" class="b J-B">B</div> <div id="C" class="c J-C">C</div> </body> |
接下來,為它們新增樣式,每個div都是全屏的,一開始只有A介面顯示,其他的都隱藏之:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
body { height: 500px; width: 100%; margin: 0; padding: 0; } div { width: 100%; height: 100%; position: absolute; font-size: 500px; text-align: center; display: none; } .a { background-color: pink; display: block; } .b { background-color: red; } .c { background-color: gray; } |
現在我們給網頁新增上行為,首先需要知道的一點是,hash指即位址列中#號後面的字串,它的改變不會引起介面的重新整理,但是會出發onhashchange事件,我們要做的就是監聽這個事件:
1 2 3 4 5 6 7 8 9 10 11 |
function hashChanged(hashObj) { //變化之後的url var newhash = hashObj.newURL.split('#')[1]; //變化之前的url var oldhash = hashObj.oldURL.split('#')[1]; //將對應的hash下介面顯示和隱藏 document.getElementById(oldhash).style.display = 'none'; document.getElementById(newhash).style.display = 'block'; } //監聽路由變化 window.onhashchange = hashChanged; |
目前,只需要以上的程式碼,我們便可以完成一個最簡單的SPA,通過位址列的變化,介面會相應地變化。當然,除了手動在位址列裡面改變hash的變化,我們也可以用程式碼改變它的變化,從而推動介面變化,下面是兩種方式的效果圖:
可以看到,左邊是在瀏覽器中直接修改hash引起了介面的變化,而右邊則是通過程式碼控制介面變化。這兩種方式都可以在history中留下痕跡,從而當我們店家回退/前進按鈕的時候追溯到之前的介面去。有些平臺不允許我們去手動修改位址列,(比如微信),那麼我們一般採用第二種方式即可。況且,比較少的使用者會去修改位址列。
下面貼出所有的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<!DOCTYPE html> <html> <head> <title></title> <style type="text/css"> body { height: 500px; width: 100%; margin: 0; padding: 0; } div { width: 100%; height: 100%; position: absolute; font-size: 500px; text-align: center; display: none; } .a { background-color: pink; display: block; } .b { background-color: red; } .c { background-color: gray; } </style> </head> <body> <div id="A" class="a J-A">A</div> <div id="B" class="b J-B">B</div> <div id="C" class="c J-C">C</div> </body> <script type="text/javascript"> function hashChanged(hashObj) { //變化之後的url var newhash = hashObj.newURL.split('#')[1]; //變化之前的url var oldhash = hashObj.oldURL.split('#')[1]; //將對應的hash下介面顯示和隱藏 document.getElementById(oldhash).style.display = 'none'; document.getElementById(newhash).style.display = 'block'; } //監聽路由變化 window.onhashchange = hashChanged; </script> </html> |
SEO優化
由於我們在處理單頁應用的時候頁面是不重新整理的,所以會導致我們的網頁記錄和內容很難被搜尋引擎抓取到。搜尋引擎抓取頁面首先要遵循http協議,可是#不是協議內的內容。而實際上也是這樣,我們沒有見過搜尋引擎的搜尋結果中,哪一條記錄可以快速定位到網頁內的某個位置的。解決的方法是用 #!號代替#號,因為谷歌會抓取帶有#!的URL。(Google規定,如果你希望Ajax生成的內容被瀏覽引擎讀取,那麼URL中可以使用”#!”(這種URL在一般頁面一般不會產生定位效果)),這樣我們可以解決ajax的不被搜尋引擎抓取的問題。在vueJs裡面,我們可以看到作者就是這樣做的。
結束
以上就是利用hash原理實現的一個很簡單的SPA。當然,要實現專案中的單頁應用,還有很多工作要做。比如傳參,動畫,非同步資源載入的問題都是需要解決的。滷煮此處只是示範了一個很簡單的例子,希望在做不復雜又不想引入其他框架的同學提供一點思路。另外單頁雖好,但不要亂用哦,根據專案的具體需求制定對應的解決方案而不是一味的追潮逐流。
參考資料
- 搜尋引擎會不會抓取帶#號(雜湊值)的URL
- 基於MVC的JavaScript Web富應用開發
- URL的井號