關於單頁應用(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制作基於兩個原因:一、希望瀏覽博客時不論怎么跳轉,可以不中斷播放音樂;二、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中的頁面超鏈接綁定事件

 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子頁面。

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

 iframe高度不能根據內容自適應,需要在子頁面load之后刷新iframe的高度

 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 }

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

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

 

2、考慮到搜索引擎收錄的超鏈接應該是非hash模式的url,比如用戶看到的是movesun.com/#/blog/list ,而實際收錄的卻是movesun.com/blog/list,通過site:movesun.com指令搜索也可以看到

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM