上兩篇文章說過要寫一個簡單的單頁應用例子的, 遲遲沒有兌諾, 實在有愧 哈哈。這篇寫給小白用戶哈。html
正好趁今天風和日麗,事情很少, 把從項目裏的代碼扣取了一下, 整理了一個簡單的例子。
由於咱們生產項目用到es6 還有構建工具,爲了讓例子足夠簡單和原生,除了一個zepto,連require都是我以前寫的文章裏的實現的,很簡單70行代碼。html5
github:https://github.com/skyweaver213/simple-spagit
demo: https://skyweaver213.github.io/simple-spa/example/demo/index.htmles6
單頁應用是指在瀏覽器中運行的應用,它們在使用期間不會從新加載頁面。像全部的應用同樣,它旨在幫助用戶完成任務,好比「編寫文檔」或者「管理Web服務器」。能夠認爲單頁應用是一種從Web服務器加載的富客戶端。(摘自:http://blog.csdn.net/zuoninger/article/details/38842823)github
單頁應用 說白了就是不用刷新頁面,例如首頁點擊 查詢到 列表頁,通常給一個過場動畫,從而優化用戶的體驗。可是每每單頁應用首次加載時間比較長,由於不少人每每把代碼都打進首屏裏了。web
而後能不能作到既要極速打開首屏體驗,也要保留單頁的流程用戶體驗呢?ajax
這時候 極致的按需, 能把這個問題 迎刃而解。api
截圖爲例, 例如綠色的其實就是用戶打開這個頁面首先最關心的部分,咱們把這部分納入到首屏裏,而後待首屏渲染成功後,再異步 require 其餘首屏之額的模塊,例如我藍色框住的信息和操做。瀏覽器
例如這樣。服務器
//航班卡片渲染 flightInfo.render(orderDetail, orderDetailInfo); //價格render pricetotal.render(orderDetail, orderDetailInfo); //異步模塊加載 this.loadAsynModules(); this.loadAsynXprdModules();
一、單頁的核心實現
利用了瀏覽器 支持歷史記錄操做,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;