上兩篇文章說過要寫一個簡單的單頁應用例子的, 遲遲沒有兌諾, 實在有愧 哈哈。這篇寫給小白使用者哈。
正好趁今天風和日麗,事情不多, 把從項目裡的代碼扣取了一下, 整理了一個簡單的例子。
因為我們生産項目用到es6 還有建構工具,為了讓例子足夠簡單和原生,除了一個zepto,連require都是我之前寫的文章裡的實作的,很簡單70行代碼。
事例位址
github:https://github.com/skyweaver213/simple-spa
demo: https://skyweaver213.github.io/simple-spa/example/demo/index.html
單頁應用
單頁應用是指在浏覽器中運作的應用,它們在使用期間不會重新加載頁面。像所有的應用一樣,它旨在幫助使用者完成任務,比如“編寫文檔”或者“管理Web伺服器”。可以認為單頁應用是一種從Web伺服器加載的富用戶端。(摘自:http://blog.csdn.net/zuoninger/article/details/38842823)
如何實作
單頁應用 說白了就是不用重新整理頁面,例如首頁點選 查詢到 清單頁,一般給一個過場動畫,進而優化使用者的體驗。但是往往單頁應用首次加載時間比較長,因為很多人往往把代碼都打進首屏裡了。
然後能不能做到既要極速打開首屏體驗,也要保留單頁的流程使用者體驗呢?
這時候 極緻的按需, 能把這個問題 迎刃而解。

截圖為例, 例如綠色的其實就是使用者打開這個頁面首先最關心的部分,我們把這部分歸入到首屏裡,然後待首屏渲染成功後,再異步 require 其他首屏之額的子產品,例如我藍色框住的資訊和操作。
例如這樣。
//航班卡片渲染
flightInfo.render(orderDetail, orderDetailInfo);
//價格render
pricetotal.render(orderDetail, orderDetailInfo);
//異步子產品加載
this.loadAsynModules();
this.loadAsynXprdModules();
1、單頁的核心實作
利用了浏覽器 支援曆史記錄操作,pushState 和popstate (如果不支援這個屬性的浏覽器可以用hashchange這個事件代替,移動端基本都支援了)
沒了解過這個屬性的 可以看看 張鑫旭的 :http://www.zhangxinxu.com/wordpress/2013/06/html5-history-api-pushstate-replacestate-ajax/
流程大概是這樣
拉取html:
1 var me = this;
2 //擷取html
3 $.ajax({
4 url: htmlPath,
5 type: 'get',
6 success: function (data) {
7 //console.log('ss data ', data);
8 getScript(data, me);
9 },
10 error: function (e) {
11 console.log('error ', e);
12 }
13
14 });
html 内容:
1 <div class="main-viewport">
2 <div class="viewport index-viewport" data-no-init="true">
3 <style>
4 .index-viewport {
5 background: cornflowerblue;
6 }
7 </style>
8
9 <div class="viewport-content">
10 index
11
12 <button class="js_tolist"> to list </button>
13 </div>
14
15
16 <script type="text/fscript" data-src="./js/index.js" ></script>
17 </div>
18 </div>
注意 script是 data-src ,因為是單頁代碼動态引入,然後拉取html成功後 會自動關聯viewport下的script,再讀取data-src的内容,然後動态引入script資源。
動态引入js:
1 //取scripts的絕對路徑 如果不是絕對路徑 ,需要動态計算
2 var script_path = scripts[0].getAttribute('data-src') || '';
3
4 require([script_path], function (exports) {
5
6 var view = cloneObj(exports.view);
7
8 view.htmlPath = htmlPath;
9 view.viewPort = $("[page-path='" + htmlPath + "']");
10 view.$ = function (selector) {
11 return view.viewPort.find(selector);
12 };
13 //每建立一個view 把引用存起來
14 DF.VIEWREF[htmlPath] = view;
15
16 //如果上一個view存在
17 scope.preView && scope.preView.onHide.apply(scope.preView);
18
19 //加載頁面完成後
20 scope.fishLoad(htmlPath, pushState, search);
21
22
23 view && view.onCreate.apply(view);
24 //綁定事件
25 if (view && view.events) {
26 DF.bindEventAction.call(view, view.events);
27 }
28
29
30 view && view.onShow.apply(view);
31 if (view.onAppear) {
32 DF.onAppear = view.onAppear;
33 }
34 scope.updatePageid(view);
35
36
37 });
js動态引入成功後,再執行單頁的生命周期。
分别是
onCreate 頁面建立的時候執行, 隻執行一次
onShow 頁面建立後會執行, 每次切換下一個頁面會執行
onHide 頁面切換的時候,上一個頁面隐藏的時候會執行
然後資源加載成功,頁面渲染成功後, 會pushSate,修改浏覽器位址
1 //fishLoad
2 fishLoad: function (htmlPath, pushState, search) {
3 //記錄上一個頁面
4 if (this.curView) {
5 this.preView = this.curView;
6 } else {
7 this.preView = DF.VIEWREF[htmlPath];
8 }
9
10 search = search || '';
11
12 //設定目前view
13 this.curView = DF.VIEWREF[htmlPath];
14
15 //console.log('curview ', this.curView.htmlPath, ' preView ', this.preView.htmlPath)
16
17 //渲染成功
18 if (pushState) {
19 history.pushState({
20 'viewPath': htmlPath,
21 'pro': 'spa',
22 'url': htmlPath
23 }, "頁面标題", htmlPath + search);
24 }
25
26 },
back回退的:
1 back: function () {
2 history.back();
3 },
4
5 //history.back 會觸發 popstate,然後會重新走loadView邏輯
6 $(window).bind("popstate.pageview", function (e) {
7
8 //是單頁spa操作 才觸發
9 if (this.getViewPath(location.href) != this.curView.htmlPath) {
10 /*
11 * 該幹嘛幹嘛
12 */
13 var pat = new RegExp(FHYBRID.appPath, 'i');
14 DF.loadView(location.pathname.replace(pat, ''), false, false, true);
15 }
16
17 }.bind(this));
動畫過渡:
1 //動畫執行方法
2 /**
3 * @param animInitClass 動畫初始化時候的class
4 * @param animBeforeClass 動畫執行前的class
5 * @param animEndClass 動畫執行後的class
6 */
7 viewAnimation: function (opt) {
8 var $static_el = opt.staticView;
9 var $anim_el = opt.animView;
10 var $anim_init_class = opt.animInitClass;
11 var $anim_before_class = opt.animBeforeClass;
12 var $anim_end_rmclass = opt.animEndClass;
13 var anim_type = opt.animType || 'in'; //動畫是進入 還是 出 ; in or out
14
15 $anim_el.addClass($anim_init_class);
16
17 //進入 動畫節點 顯示, 出, 對上一個頁面顯示
18 (anim_type == 'in') ? $anim_el.show() : $static_el.show();
19
20
21 setTimeout(function () {
22 $anim_el.addClass($anim_before_class);
23
24 $anim_el.on('webkitTransitionEnd', function () {
25 //進入 對上一個頁面隐藏; 出 動畫節點 隐藏,
26 (anim_type == 'in') ? $static_el.hide() : $anim_el.hide();
27
28 $anim_el.removeClass($anim_end_rmclass);
29 $anim_el.off('webkitTransitionEnd');
30 });
31 }, 0);
32
33
34 },
調用:
1 //切換動畫
2 this.viewAnimation({
3 staticView: this.preView.viewPort,
4 animView: this.curView.viewPort,
5 animType: 'in',
6 animInitClass: 'ui-page-right ui-transition',
7 animBeforeClass: 'ui-page-center-i',
8 animEndClass: 'ui-page-right ui-transition ui-page-center-i'
9 });
最後 請看我上面的事例,一共三個頁面:
index.html
list.html
booking.html
首先執行 index onCreate -> 然後執行 index onShow ;點選tolist; 執行 index onHide 然後執行 list onCreate-> list onShow;