HTML5 History API提供了一種功能,能讓開發人員在不刷新整個頁面的情況下修改站點的URL。這個功能很有用,例如通過一段JavaScript代碼局部載入頁面的內容,你希望通過改變當前頁面的URL來反應出頁面內容的變化,這時該功能可以派上用場。 舉個例子,當用戶從首頁進入幫助頁面時,我們通 ...
HTML5 History API提供了一種功能,能讓開發人員在不刷新整個頁面的情況下修改站點的URL。這個功能很有用,例如通過一段JavaScript代碼局部載入頁面的內容,你希望通過改變當前頁面的URL來反應出頁面內容的變化,這時該功能可以派上用場。
舉個例子,當用戶從首頁進入幫助頁面時,我們通過Ajax來載入幫助頁面的內容。然後這個用戶又轉到產品頁面,我們需要再一次通過Ajax請求來替換頁面的內容。當用戶想分享頁面的URL時,通過History API,我們可以改變頁面的URL來反應內容的修改,這樣不管是用戶分享還是保存的URL都能和頁面的內容對應起來。
基本知識
要查看這個API提供了哪些功能非常簡單,打開瀏覽器的Developer Tools工具面板,然後在console中輸入history。如果你的瀏覽器支持History API,你將會看到這個對象下麵附帶了很多方法。
註意其中的pushState和replaceState這兩個方法。我們可以在console中進行一些簡單的測試,來看看當我們使用這兩個方法時URL會發生什麼變化。稍後我們將分析這兩個方法中的所有參數,現在我們只需要關註最後一個參數:
history.replaceState(null, null, 'hello');
上面代碼中的replaceState方法改變了當前頁面的URL,在後面添加了一個'/hello'。不過並沒有發出任何request請求,當前視窗仍然停留在之前的頁面。不過這裡有個問題,當你點擊瀏覽器的後退按鈕時,頁面並不會回退到我們通過replaceState方法修改之前的那個URL,而是直接回退到了上一個頁面(即我們進入到這個頁面之前的那個頁面)。這是因為replaceState方法不會修改瀏覽器的history,它只是簡單地替換了地址欄中的URL。
要解決這個問題我們需要使用pushState方法:
history.pushState(null, null, 'hello');
現在再點擊瀏覽器的後退按鈕,你會發現它和你預想的效果一樣。因為pushState方法將我們傳給它的URL添加到瀏覽器的history中,從而改變了瀏覽器的history。假如我們將另外一個完整的站點URL傳遞給它會發生什麼情況呢?例如我們在baidu.com的首頁進行測試,然後在console中輸入下麵的內容。
history.pushState(null, null, 'https://twitter.com/hello');
瀏覽器會報錯。因為傳遞給pushState方法的URl必須和當前頁面的URL屬於同一個源(即不能跨域),否則會有很大的安全漏洞,開發人員可能會借用該功能來欺騙用戶,讓他們覺得自己是在訪問一個完全不同的站點,而事實並非如此。
來看看傳遞給pushState方法的所有參數:
history.pushState([data], [title], [url]);
- 第一個參數用來傳遞我們需要的數據,當頁面的狀態發生變化時我們可以接收到該數據。如用戶點擊瀏覽器的後退和向前按鈕。需要註意的是在Firefox中只允許傳遞最多640K的數據。
- 第二個參數title是一個字元串,不過截止到目前,幾乎所有的瀏覽器都忽略該參數。
- 最後一個參數是我們想要替換的URL。
簡單回顧一下
這些History API最主要的功能就是不重新載入頁面。以往我們只能通過改變window.location的值來修改當前頁面的URL,不過這會導致整個頁面被重新載入。如果你修改的只是URL中的hash,則不會導致頁面被刷新。
使用舊的hashbang方法可以改變頁面的URL而不刷新頁面。著名的Twitter就是使用的該方法,不過也廣受詬病,畢竟hash在location中並不被作為一個真正的資源來對待。
作為History API的早期支持者,Twitter後來拋棄了傳統的hashbang方法。在2012年,Twitter的團隊介紹了他們的新方法,併列出了其中的一些問題同時還詳細地介紹了各瀏覽器應該如何實現該規範。
一個使用pushState和Ajax的例子
https://css-tricks.com/examples/State/
在該示例中,我們希望用戶通過我們的網站找到電影捉鬼敢死隊(一部美國電影)中的演員。當用戶選擇一個圖片時,我們需要在下方顯示該演員對應的文字描述,同時給該圖片一個被選中的效果。當點擊後退按鈕時,頁面應該切換到上一個被選中的圖片狀態,同時圖片下方的文字也要一併切換。當點擊前進按鈕時也一樣。
這裡有一個效果圖:
這個示例的HTML代碼非常簡單:div.gallery中包含了所有的鏈接,每個鏈接里有一個圖片。接下來我們放置了一個空的div.content,用來存放當演員圖片被點擊時顯示在下放的文字。
<div class="gallery"> <a href="https://cdn.css-tricks.com/peter.html"> <img src="bill.png" alt="Peter" class="peter" data-name="peter"> </a> <a href="https://cdn.css-tricks.com/ray.html"> <img src="ray.png" alt="Ray" class="ray" data-name="ray"> </a> <a href="https://cdn.css-tricks.com/egon.html"> <img src="egon.png" alt="Egon" class="egon" data-name="egon"> </a> <a href="https://cdn.css-tricks.com/winston.html"> <img src="winston.png" alt="Winston" class="winston" data-name="winston"> </a> </div> <p class="selected">Ghostbusters</p> <p class="highlight"></p> <div class="content"></div>
如果沒有JavaScript該頁面仍然可以正常工作,點擊圖片可以跳轉到對應的頁面,然後點擊後退按鈕也可以回到之前的頁面。這是為了考慮頁面的可訪問行和優雅降級。
接下來我們要添加JavaScript代碼了。我們通過event propagation給div.gallery元素中的每一個link添加一個事件處理程式,像這樣:
var container = document.querySelector('.gallery'); container.addEventListener('click', function(e) { if (e.target != e.currentTarget) { e.preventDefault(); // e.target is the image inside the link we just clicked. } e.stopPropagation(); }, false);
在if語句中,我們獲取到被選中圖片的data-name屬性的值,然後將'.html'添加到後面拼成一個要訪問的頁面地址,並將其作為第三個參數傳遞給pushState方法(不過在真實的例子中我們可能會在Ajax請求成功之後才會去修改URL)。
var data = e.target.getAttribute('data-name'), url = data + ".html"; history.pushState(null, null, url); // 此處更改當前的classes樣式 // 然後使用data變數的值更新 // 並通過Ajax請求.content元素的內容 // 最後再更新當前文檔的title
(當然,此處我們也可以直接使用link的href屬性的值)
我將真實代碼中的內容都替換成註釋了,這樣我們可以只關註pushState方法的使用。
現在我們點擊圖片,URL和Ajax請求的內容會被自動更新,但是當我們點擊後退按鈕時並不會回退到之前選中的演員圖片。這裡我們還需要在用戶點擊後退和前進按鈕時使用另外一個Ajax請求來更新內容,並再一次使用pushState方法來更新頁面的URL。
我們使用pushState方法中的第一個參數(其中的state)來保存狀態信息:
history.pushState(data, null, url);
上面代碼中的data參數在popstate事件觸發時可以被獲取到。當瀏覽器的後退和前進按鈕被點擊時會觸發popstate事件。
window.addEventListener('popstate', function(e) { // e.state表示上一個被點擊的圖片的data-attribute });
我們可以通過該參數傳遞一些我們需要的信息,例如在該示例中我們將之前選中的捉鬼敢死隊的演員作為參數傳遞給requestContent方法,在該方法中,我們使用jQuery的load方法進行一次Ajax請求。
function requestContent(file) { $('.content').load(file + ' .content'); } window.addEventListener('popstate', function(e) { var character = e.state; if (character == null) { removeCurrentClass(); textWrapper.innerHTML = " "; content.innerHTML = " "; document.title = defaultTitle; } else { updateText(character); requestContent(character + ".html"); addCurrentClass(character); document.title = "Ghostbuster | " + character; } });
如果用戶點擊了演員Ray的圖片,event listener會被觸發,然後在pushState事件中保存圖片的data屬性的值。當用戶點擊另外一個圖片,並點擊了瀏覽器的後退按鈕,此時popstate事件會被觸發,從而重新載入ray.html頁面。
這意味著什麼呢?當我們點擊一個演員的圖片然後將被更改的URL分享出去,用戶訪問這個URL時對應的HTML文件會被自動載入進來。這會帶來一些更好的用戶體驗,並保證了URL和頁面內容的一致性從而減少了因此而帶給用戶的一些困惑。
上面的示例只是簡單地通過jQuery來動態載入內容,我們當然也可以在pushState方法中傳遞一些更加複雜的對象。不過這個例子已經能足夠說明問題並幫助我們開始學習如何使用History API的功能。我們先要學會走,然後才能跑。
下一步
如果我們想大範圍地使用這種技術,我們應該考慮使用一些專有的工具,例如pjax。 它是一個jQuery的插件,使用它可以大大提高我們同時使用Ajax和pushState方法進行開發的速度,不過它只支持那些使用History API介面的現代瀏覽器。
History JS可以相容舊瀏覽器,對於不支持History API介面的瀏覽器,它依然使用舊的URL hash的方式來實現同樣的功能。
有關URLs
這裡我特別引用了Kyle Neath有關URLs的說明:
URLs是一個通用的概念,它可以工作在Firefox, Chrome, Safari, Internet Explorer, curl, wget,甚至在你的iPhone, Android以及便簽紙上。它是web中的一個通用的語法。不要認為這是理所當然的。任何一個稍微懂點技術的用戶都可以瀏覽你的應用的90%以上的部分而不用去刻意記住那些URL的結構。要實現這樣的效果,你需要考慮URLs的實用性。
這意味著不論你想要進行什麼樣的hacks或性能優化,作為web開發人員,你應該註重URL。而隨著HTML5 History API的幫助,我們可以輕鬆地解決諸如上述示例中的一些問題。
常見問題
- 將Ajax請求的地址嵌入到a標記的href屬性中通常是個不錯的主意。
- 確保在JavaScript的click事件處理程式中return true,這樣當有人使用中鍵點擊或命令點擊時不會導致程式被意外覆蓋。
補充
- Mozilla有關操作瀏覽器history的文檔
- Ajax示例集錦Dive into HTML5
- Twitter有關pushState的實現
瀏覽器支持
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |
原文地址:https://css-tricks.com/using-the-html5-history-api/