不知你有沒(méi)有發(fā)現(xiàn),像Github、百度、微博等這些大站,已經(jīng)不再使用普通的a標(biāo)簽做跳轉(zhuǎn)了。他們大多使用Ajax請(qǐng)求替代了a標(biāo)簽的默認(rèn)跳轉(zhuǎn),然后使用HTML5的新API修改了Url,你可以在F12的Network面板里發(fā)現(xiàn)這個(gè)秘密。
這項(xiàng)技術(shù)并沒(méi)有特別標(biāo)準(zhǔn)的學(xué)名,大家都稱呼為Pjax,意為PushState + Ajax。這并不完全準(zhǔn)確,因?yàn)檫€有Hash + Ajax等方法,但為了方便,我們下文還是統(tǒng)稱為Pjax。
Pjax是一個(gè)優(yōu)秀的解決方案,你有足夠多的理由來(lái)使用它:
Pjax的原理十分簡(jiǎn)單。
1. 攔截a標(biāo)簽的默認(rèn)跳轉(zhuǎn)動(dòng)作。
2. 使用Ajax請(qǐng)求新頁(yè)面。
3. 將返回的Html替換到頁(yè)面中。
4. 使用HTML5的History API或者Url的Hash修改Url。
我們來(lái)看看HTML5在History里增加了什么:
history.pushState(state, title, url)
pushState方法會(huì)將當(dāng)前的url添加到歷史記錄中,然后修改當(dāng)前url為新url。請(qǐng)注意,這個(gè)方法只會(huì)修改地址欄的Url顯示,但并不會(huì)發(fā)出任何請(qǐng)求。我們正是基于此特性來(lái)實(shí)現(xiàn)Pjax。它有3個(gè)參數(shù):
history.replaceState(state, title, url)
replaceState方法與pushState大同小異,區(qū)別只在于pushState會(huì)將當(dāng)前url添加到歷史記錄,之后再修改url,而replaceState只是修改url,不添加歷史記錄。
window.onpopstate 事件
一般來(lái)說(shuō),每當(dāng)url變動(dòng)時(shí),popstate事件都會(huì)被觸發(fā)。但若是調(diào)用pushState來(lái)修改url,該事件則不會(huì)觸發(fā),因此,我們可以把它用作瀏覽器的前進(jìn)后退事件。該事件有一個(gè)參數(shù),就是上文pushState方法的第一個(gè)參數(shù)state。
這里我們以daipig為例,打開(kāi)daipig,地址欄是http://www.0755xucheng.com 。接下來(lái)打開(kāi)F12 Console,輸入:
history.pushState({ a: 1, b: 2 }, null, "http://www.0755xucheng.com/abcdefg");
可以發(fā)現(xiàn),url已經(jīng)變成我們輸入的url了,但頁(yè)面并沒(méi)有刷新,也沒(méi)有發(fā)出任何請(qǐng)求。現(xiàn)在再輸入history.state,就可以看到我們剛剛傳過(guò)來(lái)的第一個(gè)參數(shù)state了。
這時(shí)點(diǎn)擊后退,url會(huì)回到www.0755xucheng.com,同樣是沒(méi)有刷新。只不過(guò)后退的時(shí)候其實(shí)是觸發(fā)了window.onpopstate事件的。
詳細(xì)文檔可以查閱MDN: https://developer.mozilla.org/zh-CN/docs/DOM/Manipulating_the_browser_...
Pjax的原理上文已經(jīng)講了,并不復(fù)雜。我實(shí)現(xiàn)了一個(gè)比較粗糙的Pjax庫(kù),已經(jīng)能滿足不少需求,如果你有興趣,可以上Github幫忙完善一下代碼。地址是:https://github.com/Coffcer/coffce-pjax 。
完整的代碼見(jiàn)Github,這里我們只談需要注意的一些地方。
要實(shí)現(xiàn)Pjax,難免就會(huì)有匹配選擇器的需求。你需要判斷當(dāng)前點(diǎn)擊的元素,是否匹配指定選擇器。這里我給出一個(gè)兼容至IE8的解決方法:
// 判斷element是否匹配選擇器
在現(xiàn)代瀏覽器上,優(yōu)先使用原生的matchesSelector方法來(lái)判斷,在IE8及以下的瀏覽器里,循環(huán)document.querySelector的結(jié)果集,依次對(duì)比。
這個(gè)方法利用了閉包,然后重寫(xiě)自身,只有在第一次調(diào)用時(shí)需要判斷加哪個(gè)前綴執(zhí)行哪個(gè)方法,其后都是調(diào)用了閉包的match函數(shù)。
IE6到IE9是不支持pushState的,要修改Url,只能利用Url的Hash,也即是#號(hào)。
你可以隨意找個(gè)網(wǎng)站試一下,在url后面加上#號(hào)和任意內(nèi)容,頁(yè)面并不會(huì)刷新。此時(shí)點(diǎn)擊后退也只會(huì)回到上一條#號(hào),同樣不會(huì)刷新。
那么我們只需把pushState(新url)換成localtion.hash = 新url,把onpopstate事件換成onhashchange事件就可以兼容IE了。
QQ音樂(lè),網(wǎng)易云音樂(lè)等就是使用這種方式。
現(xiàn)成的庫(kù)
我簡(jiǎn)單實(shí)現(xiàn)了一個(gè)比較粗糙的Pjax庫(kù),地址是:https://github.com/Coffcer/coffce-pjax ,歡迎PR和Star。作者:coffee