關於單頁應用(SPA)的經驗之談

時下SPA單頁應用如火如荼,對前端乃至後端開發都帶來不小的衝擊和變革。筆者整理了下筆記,決定寫一下之前基於iframe作單頁博客的一些經驗方法。
對於單頁應用,筆者沒有找到最官方的定義。 在筆者看來,在用戶操做過程當中,瀏覽器始終不會重載整個頁面的web應用,即可以稱爲單頁應用。這裏不包括 https://im.qq.com/這種宣傳單頁
例如coding.net、網易雲音樂播放、QQ空間、移動端大量的react案例(好比手Q健康、公衆號)等等

單頁站點優劣

單頁站點的優點主要有三點     

傳輸數據少

單頁站點的重點是局部刷新,所以每次更新,傳輸的數據少,減小後端壓力,甚至對於徹底先後端分離的SPA應用,只須要傳輸少許json數據便可。這一點在移動端顯得尤其重要,許多應用前端代碼並不會頻繁更新,徹底能夠由前端直接緩存起來,每次使用只需與後端交互少許數據,這樣既省流量也能讓用戶得到接近native的體驗

服務可不中斷

一些特殊網站,如音樂播放、IM聊天等,不但願由於頁面所有重載形成服務中斷。單頁應用由於局部刷新,能夠將這些服務放置在刷新範圍以外,持續提供服務

先後端開發更規範

前端也可按照MVC的模式更好的模塊化開發,然後端開發僅僅只須要開發數據操做接口,對先後端開發而言都是一種解放
 
但單頁站點也帶來了一些新的問題,好比

首次加載數據大耗時長

特別是目前基於angular或者react的純先後端分離的SPA,結合一些javascript方言,編譯出來js至關的大,筆者曾在內網親眼目擊10M級別的js文件,即使之內網的網速首次打開也須要3秒左右。爲每一個模塊單獨編譯js是種辦法,但實際操做會可能發現,隨着項目越作越大,拆分紅獨立模塊編譯的成本會愈來愈大,最終不得不委曲求全整站使用一個js,除非從一開始就有良好的規範限制。

極差的SEO(搜索引擎優化)

衆所周知,經過請求url便可獲取到大量頁面正文文本的頁面是對搜索引擎最爲友好的,雖然如今的爬蟲已經具有解析運行頁面js腳本索引動態內容的能力,可是每一個網站千奇百怪,爬蟲須要考慮觸發什麼事件、按什麼順序觸發才能獲取更多內容,索引動態內容的難度要遠遠大於索引靜態內容。而目前主流的單頁應用,幾乎都是之前端js模板引擎來渲染html頁面數據,直接經過url獲取到的內容極少,這對搜索引擎很是不友好。SEO最差的單頁應用可能僅僅只有首頁可以被搜索引擎收錄。這成了制約單頁應用發展的一大障礙,即使如今又方案能夠提升收錄,但效果仍是很差

導航須要人爲處理

瀏覽器對div以及早期瀏覽器對iframe都不記錄歷史記錄,所以須要開發對瀏覽器的前進後退作實現,經過修改hash或者history API來實現前進後退(後面會提到)

單頁應用的實現方式

筆者瞭解到的,目前主要兩種實現方式

iframe

其一,使用iframe的優勢之一就是開發簡單,目前的瀏覽器都已經對iframe url發生修改產生歷史記錄。
其二,除了響應式問題的兼容性很差以外(也正所以iframe很不適合用在移動端),iframe做爲使用多年的瀏覽器技術之一,在許多方面的兼容性要好許多,也是一些新技術在低版本瀏覽器上不可用時的替代解決方案,如contentEditable。
其三,iframe與父文檔相對獨立,能夠不受父文檔的影響,想必這也是目前一些網站(網易雲音樂,QQ空間,各大郵箱)繼續使用iframe的主要緣由。

ajax+div+historyapi

這種方式實現要更復雜,開發要本身實現url管理,以達到前進、後退跳轉等能力,不過目前都已經有成熟的路由庫可使用,另外基於div模式的SPA,開發須要考慮全局對局部的影響,包括css、事件等。這種方式的優勢是刷新要更輕量,js庫和css樣式在首次加載便可,局部頁面能夠只加載少許的數據,而且基於div響應式效果在移動端要更好。所以這也成了目前流行的前端框架angular、react等選用的方案。

基於iframe製做單頁博客

筆者的博客製做於2015年,當時的PC瀏覽器應該不支持iframe歷史記錄,因此筆者選擇經過修改hash的方式實現歷史記錄(瀏覽器對hash的修改會記錄歷史記錄),選擇基於iframe製做基於兩個緣由:1、但願瀏覽博客時不論怎麼跳轉,能夠不中斷播放音樂;2、iframe相對全站ajax+div而言要更簡單易行。博客地址http://movesun.com,博客佈局參考  http://www.kotonohanoniwa.jp/
作法是綁定全部須要在iframe中打開的a標籤的click事件,當點擊a標籤時,將a標籤url中的path路徑修改成瀏覽器url的hash值。例如我想訪問的是  http://movesun.com/blog/list,則將/blog/list做爲hash值設置到地址欄 ,所以在瀏覽器地址欄看到的地址就變爲了 http://movesun.com/#/blog/list
 
所以在父文檔中有這樣一段js
 1 $('a[target="contentFrame"]').click(function(){
 2     var $this = $(this),
 3         url = $this.attr('href'),
 4         mainHost = location.host,
 5         i = url.indexOf(mainHost);
 6     $active.removeClass('active');
 7     $active = $this.parent('li');
 8     $active.addClass('active');
 9     if(i !== -1){
10         url = url.substr(i + mainHost.length);
11     }
12     window.location.hash = '#' + url;
13     return false;
14 });

在iframe頁面(子頁面)中,也有一段相似的js,爲iframe中的頁面超連接綁定事件javascript

 1 $('a').click(function(){
 2     var url = $(this).attr('href')
 3     if(url && url != '#' && url.indexOf('http') == 0){
 4         var mainHost = window.parent.location.host,
 5                 i = url.indexOf(mainHost);
 6         if(i !== -1){
 7             url = url.substr(i + mainHost.length);
 8         }
 9         window.parent.location.hash = '#' + url;
10     }
11     return false;
12 });

注意這兩段代碼,修改的都是父文檔(頂層窗口)地址欄的hash值。因此,只須要在父文檔中監聽onhashchange事件,在事件響應中設置iframe的src 進而load子頁面。css

1 $container = $('div.page-body > iframe');
2 window.onhashchange = function(){
3     $container.attr('src',location.hash.substring(1));
4 };

 iframe高度不能根據內容自適應,須要在子頁面load以後刷新iframe的高度html

 1 var refreshHeight = function(){
 2     var $this = $container,
 3         minHeight = $('.page-right').height() - $('.top-menu').height() - 20,
 4         contentHeight = $this.contents().find('body').height() + 10;
 5     $this.height(contentHeight < minHeight ? minHeight : contentHeight);
 6 };
 7  
 8 $container.load(function(){
 9     refreshHeight();
10 });
11 // 首次刷新,不然加載過程當中會看到白框
12 refreshHeight();

到這裏基本已經實現任意跳轉、回退、前進頁面再也不刷新整個頁面。下面的代碼是爲了解決當打開多個頂層文檔時(開多個窗口),音樂不重複播放,方法也很簡單,在localStorage中記錄頂層文檔的數量,每開一個新窗口加1,關閉時減1,只要記錄數大於1便不自動播放。 前端

 1 if(window.localStorage){
 2     var $window = $(window);
 3     $window.on('beforeunload',function(){
 4         console.log('-1');
 5         localStorage.framePage = localStorage.framePage - 1;
 6     });
 7  
 8     window.addEventListener("storage", function(e){
 9         console.log("oldValue: "+ e.oldValue + " newValue:" + e.newValue)
10     });
11 }
12 var autoplay =  location.host !== 'movesun.qq.com';
13 if(window.localStorage){
14     if(Number(localStorage.framePage) >= 1){
15         autoplay = false;
16     }
17  
18     if(isNaN(localStorage.framePage) || Number(localStorage.framePage) <= 0) localStorage.framePage = 1;
19     else {
20         localStorage.framePage = Number(localStorage.framePage) + 1;
21     }
22 }

博客依然有兩個問題須要解決java

一、目前的瀏覽器已經支持記錄iframe變動的歷史記錄,經過hash記錄歷史就顯的沒有必要了。目前網站每次跳轉實際產生了兩條歷史記錄。後面找時間移出hash記錄或者禁用iframe歷史記錄react

 

二、考慮到搜索引擎收錄的超連接應該是非hash模式的url,好比用戶看到的是movesun.com/#/blog/list ,而實際收錄的倒是movesun.com/blog/list,經過site:movesun.com指令搜索也能夠看到web

直接訪問這類url地址,將直接打開iframe裏的內容,因此,當用戶直接點擊搜索引擎的結果進入博客時,應該將用戶跳轉到hash模式,頁面才能正常顯示,但這樣對搜索引擎而言,會陷入一個無限循環,影響搜索引擎收錄。ajax

前端因直接面向用戶,使得技術也更新迭代的頻繁,前端開發人員也須要不斷學習以追趕時代的潮流。而反觀後臺技術,十年來都沒什麼及其巨大的變化,不少技術經久不衰,後端開發徹底有一招鮮吃遍天的架勢。這也是的前端人員比較搶手,在一些公司都存在前端與後臺人力嚴重不平衡的現像,十幾位後臺搭配一位前端的事情,也不是沒有,奇貨可居,優秀的前端是很是吃香的。json

相關文章
相關標籤/搜索