增加了頁面切換效果后,再配合手勢以及按鍵,讓單頁面app更像一個原生app浮创, 然而卻引來了一個新的問題, 通過瀏覽器自帶的前進后退無法有選擇的采取動畫效果砌函。因為在popstate的時候斩披,都是采用一種動畫。如果能夠在popstate事件中能夠知道當前頁面以及要更換的頁面的位置讹俊,采取相應的動畫的垦沉,那就可以解決這個問題。由于瀏覽器的history的棧無法讀取仍劈, 我們就創(chuàng)建一個History對象厕倍, 執(zhí)行的過程與瀏覽器的原生行為一致, 通過取當前頁面在創(chuàng)建的History的位置贩疙, 以及它的前后的頁面與要切換的頁面進行比較位置關系讹弯。
通過第六篇已經(jīng)了解瀏覽器原生的history棧的原理况既。這里創(chuàng)建一個History對象,構造函數(shù)代碼如下
function History() {
this.history = [];
this.index = null;
}
history的數(shù)組代表瀏覽器中的頁面的url组民, index表示當前頁面的url的索引值棒仍。
對應的pushState方法如下
pushState: function (str) {
if (this.index !== null) {
var len = this.history.length;
var nextIndex = this.index + 1;
this.history.splice(nextIndex, this.len - nextIndex, str);
this.index = nextIndex;
}
else {
this.history.push(str);
this.index = 0;
}
return str;
},
replaceState的方法如下
replaceState: function (str) {
if (this.index) {
this.history.splice(this.index, 1, str);
}
else {
this.history.push(str);
this.index = 0;
}
return str;
},
這兩個方法完全照瀏覽器的原理而創(chuàng)建的
對應的還有back以及forward, 在單頁面中邪乍, 原生瀏覽器history的go方法不常用降狠,因此不實現(xiàn), back方法和forward方法代碼如下:
back: function () {
if (this.index === null) return "";
return this.history[this.index === 0 ? 0 : --this.index];
},
forward: function () {
if (this.index === null) return "";
var len = this.history.length;
return this.history[this.index === len - 1 ? len - 1 : ++this.index];
},
返回history索引值的url字符串
接著實現(xiàn)獲取當前索引值前后位置的url值庇楞,代碼如下:
getSurround: function () {
var len = this.history.length;
if (this.index === null) {
return {
next: "",
prev: ""
};
}
else if (this.history.length === 1) {
return {
next: "",
prev: ""
};
}
else if (this.index === 0) {
return {
next: this.history[1],
prev: ""
};
}
else if (this.index === len - 1) {
return {
next: "",
prev: this.history[len - 2]
}
}
else {
return {
next: this.history[this.index + 1],
prev: this.history[this.index - 1]
}
}
}
這里的情況比較多榜配,不過思路很簡單, 接著修改App對象吕晌,將History對象組合在App對象中蛋褥,App對象的構造函數(shù)加入一個history,放置History對象
function App(options) {
options = options || {};
App.extend(options, {
appClass: "app",
changeClass: "app-change",
backClass: "app-back",
changeState: "change-state",
pageInReverse: "page-in-reverse",
pageOutReverse: "page-out-reverse",
pageIn: "page-in",
pageOut: "page-out"
});
this.options = options;
this.currentPage = null;
this.staticPage = null;
this.pageContainer = null;
this.routeObj = {};
this.history = new History();
}
然后修改_attachHistory方法睛驳,將對應的方法在自定義的history對象中補上烙心,代碼如下:
_attachHistory: function (page, isBack) {
var newUrl = page.url;
if (isBack) {
history.replaceState({data: newUrl}, "", newUrl);
this.history.replaceState(newUrl);
}
else {
history.pushState({data: newUrl}, "", newUrl);
this.history.pushState(newUrl);
}
},
接著修改initialize方法中的popstate事件,因為這里不僅有history操作乏沸,還要分別獲取當前頁面的前后頁面的url淫茵,代碼如下:
window.addEventListener("popstate", function (ev) {
if (ev.state && ev.state.data) {
var url = ev.state.data;
var page = that.routeObj[url];
var urlObj = that.history.getSurround();
if (urlObj.prev == url) {
that.history.back();
that.isRenderBack = true;
}
else if (urlObj.next === url) {
that.history.forward();
that.isRenderBack = false;
}
that._renderPage(page);
}
}, false);
首先獲取當前頁面的前后的url, 判斷下一個即將渲染的頁面蹬跃,如果是后退操作匙瘪, 自定義的history執(zhí)行back方法,反之執(zhí)行前進方法蝶缀, 然后設置不同的動畫方式丹喻。
當然這里有個特殊情況, 就是如果當前頁面的前后頁面的url都相同, 然而這個情況在正常情況下可以完全被避免的翁都。 假如在a頁面碍论,pushstate到b頁面,然后在pushstate到a頁面柄慰,接著執(zhí)行history.back()操作鳍悠。這一系列的操作方式是可以直接用a頁面, pushstate到b頁面坐搔,在執(zhí)行history.back(), 接著執(zhí)行history.forward()來代替贼涩。
除了前后切換的效果之外,還有一種就是直接替換完全不變薯蝎,相當于第二章那種的遥倦,html完全替換, 因此引入了renderInstance方法,因此為了修改代碼如下:
renderBack: function (page, isBack) {
this.isRenderBack = true;
this._render(page, isBack);
},
render: function (page, isBack) {
this._render(page, isBack);
},
renderInstance: function (page, isBack) {
this._render(page, isBack, true);
},
_render: function (page, isBack, instance) {
if (typeof page === "string") page = this.routeObj[page];
if (page === this.currentPage) return;
this.routeObj[page.url] = page;
this._attachHistory(page, isBack);
this._renderPage(page, instance);
this.pageContainer.scrollTop = 0;
},
_renderPage: function (page, isInstance) {
if (this.currentPage) this.currentPage._dispose();
this.currentPage = page;
page.app = this;
var that = this;
document.title = page.title;
page.render(function (html) {
if (isInstance) {
var changeDom = that.changeDom;
changeDom.innerHTML = html;
page._initialize(changeDom);
}
else {
var backDom = that.backDom;
backDom.innerHTML = html;
that._replaceDom();
page._initialize(backDom);
}
});
},
原理非常的的簡單袒哥,通過一個布爾值來選擇那種切換形式缩筛。
這里還可以通過增加方法,或者在初始化中傳入不同的動畫類來切換動畫類型堡称。讓動畫變得非常的容易瞎抛。
這一篇就是為多頁面切換的最后一篇, 做到了這篇后却紧,做到了單頁面的基礎桐臊。頁面切換的動態(tài)效果是單頁面和多頁面的最大區(qū)別,多頁面是無法做到的晓殊。雖然這里的代碼非常簡單断凶,App對象, History對象和Page對象加起來才500行代碼有余巫俺, 卻讓單調(diào)的多頁面改成交互優(yōu)雅的單頁面认烁, 這里雖然只有4個簡單的頁面, 但是這一套邏輯可以為轉(zhuǎn)化任意多個頁面的項目介汹,把它轉(zhuǎn)化為你想要的單頁面却嗡。 現(xiàn)在正是你實踐的時候,選一個自己做過的多頁面嘹承,或者隨便想一個自己想做的窗价, 可以按照1-8篇的的順序,把它們改造為單頁面叹卷。改造的效果會讓你得到真正的收獲撼港。
總結:單頁面的靈活性增加了開發(fā)的無限玩法, 不變的是css豪娜, html和js基礎, 不用多增加任何的其它的知識就能做一個優(yōu)質(zhì)的單頁面哟楷。如果你已經(jīng)通過上面的教程改編過了一個單頁面瘤载, 你覺得你自己可以把它引入項目中了, 事實告訴你卖擅, 離真正要做成一個項目鸣奔, 現(xiàn)在才剛剛開始,后面還有很多坑需要填補惩阶。在多頁面開發(fā)時挎狸,你不用關心一個頁面切換到另外一個頁面后, 之前的頁面怎么辦断楷, 然而單頁面卻要異常關心锨匆,你需要把之前頁面的無關數(shù)據(jù)(包括組件)給清理了,防止垃圾累計導致頁面的卡頓以及移動端電量的消耗冬筒, 另外就是異步的操作恐锣, 單頁面頻繁使用異步的操作茅主, 比如ajax與indexdb操作, 一個微小的異步操作就會帶來整個應用的崩潰土榴。 因此這里推薦每個Page頁面都應該是單獨的個體诀姚,它與其它頁面沒有任何關系,它只能通過App對象來選擇渲染與否玷禽。
后續(xù)更新:之前的篇章都是靜態(tài)的頁面赫段,接下來就要轉(zhuǎn)化為動態(tài)的,通過ajax與后端交互數(shù)據(jù)矢赁,然后更新前端顯示的信息糯笙。在這之前, 需要給之前創(chuàng)建的服務器加上POST應答的功能坯台。
請用移動設備打開該案例
案例鏈接
原生開發(fā)移動web單頁面(step by step)1——傳統(tǒng)頁面的開發(fā)
原生開發(fā)移動web單頁面(step by step)2——Page對象
原生開發(fā)移動web單頁面(step by step)3——App對象
原生開發(fā)移動web單頁面(step by step)4——tap事件與slide事件
原生開發(fā)移動web單頁面(step by step)5——nodejs服務器的搭建
原生開發(fā)移動web單頁面(step by step)6——history api應用
原生開發(fā)移動web單頁面(step by step)7——頁面切換動畫