現在HTML5:通過Polyfills獲得更多

現在HTML5:通過Polyfills獲得更多

戴夫沃德 | 2012年5月17日

利用 HTML5 來搭建網站和應用可能是一項艱鉅的任務。儘管現在越來越多的現代瀏覽器正在更多的支持Html5新特性,但實際上只有很少部分人能夠幸運的只需要爲這些最新的瀏覽器編寫代碼。作爲一個專業的開發者,你必須要花很多精力來調整不自由的空間排版和實現承諾過的特性以及面對現在的現實情況,這些都是因爲瀏覽器的碎片化。好消息是 IE 9 和 10 都已經支持HTML5 了,用戶可以拋棄舊版的 Internet Explorer 瀏覽器了,不過對於開發者而言他們還需要考慮支持舊版的瀏覽器。

但是,這並不意味着您不得不放棄在短期內支持HTML5。就像網站有技巧支持多種屏幕尺寸和不同級別的CSS功能之類的差異一樣,也可以實現令人驚訝的強大的跨瀏覽器HTML5支持。儘管較舊的瀏覽器缺少許多HTML5的新API,但JavaScript是一種非常靈活的語言,並且提供了追溯性地添加新功能的機會,當它們不是本地存在時。

跨瀏覽器支持

跳躍到HTML5最讓人頭痛的問題是,我們大多數人別無選擇,只能支持各種對最有用的新API幾乎或不支持的舊瀏覽器。採用新的Web技術的想法讓人聯想到了跨瀏覽器不一致性,不可維護的分支代碼,瀏覽器嗅探以及其他一系列問題的惡夢。但是,有一項評估不足的技術可以完全緩解HTML5某些新功能的這些問題,並且仍然允許您針對新的API進行開發,就好像您的所有用戶在一夜之間都升級了瀏覽器:polyfills。

Polyfilling是Remy Sharp創造的一個術語用於描述以重複缺失API的方式回填缺失功能的方法。使用這種技術,您可以編寫特定於應用程序的代碼,而不用擔心每個用戶的瀏覽器是否本機實現它。事實上,polyfills不是一種新技術,也不是綁定到HTML5。多年來,我們一直在使用諸如json2.js,ie7-js之類的polyfills,以及在Internet Explorer中提供透明PNG支持的各種回退。不同之處在於去年HTML5 polyfills的擴散。

什麼使Polyfill?

有關我正在談論的具體示例,請查看json2.js。具體來說,這是JSON.parse實現中的第一行代碼:

iftypeof JSON.parse!== 'function'){
   // Crockford的JSON.parse的JavaScript實現
}

通過使用typeof測試守護代碼,如果瀏覽器具有JSON.parse的本機實現,則json2.js不會試圖干擾或重新定義它。如果本機API不可用,那麼json2.js將以與JavaScript本機JSON API完全兼容的方式實現JSON.parse的JavaScript版本。最終,這意味着您可以在頁面中包含json2.js,並且有信心使用JSON.parse,而不考慮您的代碼運行在哪個瀏覽器中。

這顯示了polyfilling方法的優點 - 不僅提供了兼容性層,還提供了一種試圖密切反映polyfill實現的標準API的方式。因此,沒有一個站點特定的代碼需要知道或關心兼容層的存在。最終,這會產生更乾淨,更簡單的特定於應用程序的代碼,可讓您利用新的API,同時仍保持與舊版瀏覽器的兼容性。

HTML5的新語義元素

HTML5 中對於polyfil來說最簡單的特性就是設置已經增加了的語義元素,如<article>,<aside>,<header>和<time>。他們中的大多數和<div>,<span>的表現沒有區別,但是它們有自己語義化的意義。因爲這些元素是標準通用內置語言(SGML),所以好處就是像 IE6 這樣的舊瀏覽器也能夠顯示他們。不過 IE瀏覽器的奇怪之處就是它只應用那些它承認的 CSS 樣式。因此,即使舊的 IE瀏覽器顯示了 HTML5 的新語義元素,但是它仍會忽視那些用戶自定義的樣式。

幸運的是,Sjoerd Visscher 爲 IE 找到了一個簡單的解決方法,John Resig 又讓它發揚光大。在使用任意元素形式的時候調用 document.createElement(),這樣就可以讓 IE 瀏覽器運用 CSS 中所有的樣式了。

例如,在 <head> 中單獨調用 document.createElement(‘article’)就可以讓 IE 瀏覽器強制運用CSS中的 <article> 元素。

1     <html>

2       <head>

3         <title>HTML5 Now?</title>

4         <style>

5           article { margin: 0 auto; width: 960px; }

6         </style>

7         <script>

8           document.createElement('article');

9         </script>

10    </head>

11    <body>

12      <article>

13        <!-- TODO: Write article… -->

14      </article>

15    </body>

16   </html>

圖1這個對document.createElement的調用改變了Internet Explorer應用CSS樣式的方式。

當然,沒有人願意爲HTML5中添加的每種新語義元素手動添加createElement語句。把這種單調乏味的東西抽象出來就是一個polyfill發光的地方。在這種情況下,有一個稱爲html5shim(也稱爲html5shiv)的polyfill,它可以自動執行初始化Internet Explorer與新語義元素兼容性的過程。

例如,圖1中的代碼可以被重構爲使用html5shim,如圖2所示

1     <html>

2       <head>

3         <title>HTML5 Now!</title>

4         <style>

5           article { margin: 0 auto; width: 960px; }

6         </style>

7         <!--[if ltIE 9]>

8         <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>

9         <![endif]-->

10    </head>

11    <body>

12      <article>

13        <!-- TODO: Write article… -->

14      </article>

15    </body>

16   </html>

圖2使用html5shim填充

注意圍繞腳本引用html5shim的條件註釋。這確保了polyfill只會在早於版本9的Internet Explorer版本中加載和執行。沒有時間浪費下載,解析並在已爲新元素提供適當支持的瀏覽器中執行此代碼。

另一種選擇考慮

如果您對HTML5有興趣閱讀本文,您可能已經意識到或使用Modernizr。但是,您可能不知道的一件事是Modernizr具有內置的html5shim的createElement功能。如果您使用Modernizr進行功能檢測,則您已經對HTML5的語義元素具有向後兼容性。

持久的客戶端存儲

多年來,我們別無選擇,只能將特定於供應商的DOM擴展和專有插件組合在一起,以解決在瀏覽器中長期持續存在的問題。這些解決方案包括Firefox的globalStorage,Internet Explorer的userData,Cookie和插件,如Flash或Google Gears。雖然可行,但這些被黑客入侵的解決方法很繁瑣,難以維護,並且容易出錯。

爲了解決這個問題,HTML5中最受歡迎的補充之一是基於標準的API,用於在瀏覽器中持久存儲數據:localStorage。此存儲API提供了一致的客戶端 - 服務器鍵/值存儲,可以爲用戶訪問的每個網站存儲最多5 MB的隔離數據。您可以將localStorage視爲一個容易處理的巨大cookie,並且在每個HTTP請求期間不會在瀏覽器和服務器之間來回傳送。localStorage功能非常適合需要瀏覽器特定數據的任務,如記憶首選項和本地緩存遠程數據。

每個A級瀏覽器都支持localStorage功能,其中包括Internet Explorer 8,但在大多數瀏覽器的舊版本中缺少該功能。與此同時,幾種解決方案已經發展到跨瀏覽器存儲到那些舊版瀏覽器中。它們包括Remy Sharp的存儲polyfiller的簡單性,以及store.jsPersistJS提供的全面向後兼容性,以及LawnChair和AmplifyJS存儲模塊的全功能API 

例如,您可以使用AmplifyJS存儲模塊在用戶的瀏覽器中保存一些數據,而無需使用Cookie - 即使該用戶使用的是Internet Explorer 6:

1     // Sets a localStorage variable 'Name'with my name in it.

2     amplify.store('name','Dave Ward');

3     var website ={

4         name:'Encosia',

5         url:'http://encosia.com'

6     }

7     // The library takes care of serializingobjects automatically.

8     amplify.store('website', website);

在稍提取數據的時候變得非常容易:

1     // The values we stored before could thenbe used at a later time, even

2     // during a different session.

3     var $personLink = $('<a>',{

4         text: amplify.store('name'),

5         href: amplify.store('website').url

6     });

7     $personLink.appendTo('body');

同樣,關於使用localStorage或基於localStorage的API的好處是,這些數據都不需要保存在cookie中,然後與每個HTTP請求一起傳輸,也不需要調用像Flash這樣的重量級插件只是爲了存儲一些數據。數據存儲在一個真正的,孤立的本地存儲機制中,這對於本地緩存數據或開發對離線使用有着豐富支持的站點非常有用。

使用什麼?

Remy Sharp的存儲polyfiller是唯一真正符合polyfill的存儲polyfiller,因爲其他人不完全模仿HTML5 localStorage API。但是,store.js和AmplifyJS存儲模塊支持更廣泛的回退方法,以實現舊版瀏覽器的兼容性。實際上,這很難忽視。

地理位置

地理定位是另一個HTML5功能,可以用於填充。如果瀏覽器和操作系統都支持地理定位並且正在使用GPS傳感器的設備上運行,則HTML5將提供對地理位置API的訪問權限,以允許JavaScript代碼確定您的頁面正在被訪問的位置。

移動設備是基於瀏覽器的地理位置最令人印象深刻的例子。通過將其內置的GPS硬件與支持HTML5地理定位API的現代瀏覽器相結合,Android和iOS設備都能以原生應用程序的精確度支持原生HTML5地理定位。

在這些最佳環境中訪問地理位置數據所需的JavaScript就像這樣簡單:

1     navigator.geolocation.getCurrentPosition(function(position){

2       var lat =position.coords.latitude;

3       var long =position.coords.longitude;

4       console.log('Current location: ', lat, log);

5     });

這對移動應用來說非常好,但桌面硬件通常不包含GPS傳感器。然而,我們都習慣了位置感知廣告,它們一直在互聯網上跟蹤我們在桌面硬件上的時間,遠遠超過地理定位API的存在時間,所以顯然可以解決桌面瀏覽環境中缺乏GPS的問題。

在JavaScript中,目前的解決方法是在已知IP位置的數據庫中查找訪問者的IP地址。這種方法的準確度遠低於使用GPS設備的準確度,但這些數據庫通常能夠在正確的區域範圍內定位IP地址,這對於許多應用來說是足夠有用的。

您可能會注意到不會僅依靠IP地址查找的更精確的無GPS定位技術。通常,這些增強的估計是通過將可見Wi-Fi熱點標識符與熱點的這些特定組合已經物理位於過去的數據庫進行比較的新穎方法來完成的。

不幸的是,在瀏覽器中運行的JavaScript代碼目前不知道來自操作系統的數據。因此,在可預見的將來,基於Wi-Fi的技術不適用於polyfills,因此我們只能將IP查找作爲唯一選擇。


Paul Irish編寫了一個簡單的地理定位polyfill,在舊版瀏覽器和缺少GPS傳感器的硬件上提供了一定程度的地理定位。它通過使用Google的地理位置API將訪問者的IP地址轉換爲近似的物理位置來實現此目的。這是一個真正的polyfill,它將地理定位功能插入到navigator.geolocation對象中,但前提是瀏覽器本身不提供地理定位API。

瀏覽器歷史和導航

由於膚淺的DHTML效果讓位於更加結構化的客戶端功能,如基於AJAX的分頁和單頁面界面,這些結構更改開始與瀏覽器的內置導航和歷史記錄功能不同步。然後,當用戶直觀地嘗試使用其“後退”按鈕導航到前一頁或應用程序狀態時,情況變得糟糕。搜索“禁用後退按鈕”顯示了此問題困擾現代Web開發的程度。

操縱瀏覽器位置的“哈希”部分有助於解決問題的一個方面。由於哈希最初是爲了在同一頁面內的導航點之間跳轉而發生的,因此更改URL的哈希值不會像更改基礎URL前綴那樣觸發頁面刷新。利用散列屬性允許客戶端更新,以保持瀏覽器的顯示地址與沒有傳統導航事件發生的JavaScript驅動更改同步。

onhashchange事件

儘管操縱瀏覽器的哈希值得到了很好的支持,但即使超過Internet Explorer 6,監視哈希變化的標準化方法直到最近才變得更加難以捉摸。當前的瀏覽器作物支持onhashchange事件,當地址的哈希部分發生變化時觸發onhashchange事件 - 完美用於檢測用戶何時嘗試通過使用瀏覽器的導航控件瀏覽客戶端狀態更改。不幸的是,onhashchange事件只在相對較新的瀏覽器中實現,支持從Internet Explorer 8和Firefox 3.6的版本開始。

儘管onhashchange事件在舊版瀏覽器中不可用,但有些庫在舊版瀏覽器中提供了抽象層。這些兼容性Shim使用特定於瀏覽器的怪異來複制標準的onhashchange事件,即使採取每秒多次監視location.hash的方式,並且在瀏覽器中更改時沒有其他方法。

Ben Alman的jQuery Hashchange插件就是其中的一個可靠選擇,他從他流行的jQuery BBQ插件中提取了插件。Alman的jQuery Hashchange公開了一個具有非常深入的跨瀏覽器兼容性的hashchange事件。我毫不猶豫地稱它爲一個polyfill,因爲它需要jQuery,並不完全重複本機API,但如果你已經在你的頁面上使用jQuery,它會很好用。

超越HashState

操作哈希是解決客戶端狀態管理問題的良好開端,但它並非沒有缺點。劫持合法的瀏覽器導航功能不是最佳選擇,因爲基於散列的URL結構可能會導致用戶混淆並與現有的頁內導航發生衝突。

一個更基本的問題是,瀏覽器不會在HTTP請求中包含請求的URL的哈希部分。如果不訪問URL的這一部分,就不可能立即返回與用戶加入書籤,通過電子郵件接收或通過社交分享發現的頁面處於相同狀態的頁面。這導致網站別無選擇,只能在默認的初始狀態下顯示頁面,然後自動觸發一個令人震驚的轉換,直到用戶真正需要的狀態。爲了找到這種影響可用性的證據,除了對Twitter和Gawker Media的“hash bang”重新設計廣泛的負面反應之外,您還需要尋找其他方面。

輸入pushState

幸運的是,HTML5還引入了一對更高級的API,可顯着改善客戶端歷史管理情況。通常簡稱爲pushState,window.history.pushState方法和window.onpopstate事件的組合提供了異步處理瀏覽器地址的整個路徑部分的途徑,並且同樣對哈希之外的導航事件做出反應。

在GitHub上瀏覽項目的源代碼是當前使用pushState最好的真實世界示例之一。由於使用pushState操縱瀏覽器的地址不會像傳統的地址更改那樣導致整個頁面的刷新,所以GitHub能夠在每個代碼的“頁面”之間提供動畫轉換,同時仍然保留用戶友好的URL哈希或querystrings。

更好的是,如果您將書籤保存到這些URL之一併稍後直接導航到其中,則GitHub能夠在第一個請求中立即向您提供正確的內容,因爲客戶端URL結構與它們在服務器上使用的內容相匹配。正如我前面提到的,當你使用基於散列的URL時,這樣做是不可能的,因爲你的Web服務器永遠不會知道請求的散列部分。

在自己的代碼中使用onhashchange和pushState

不幸的是,要真正將pushState功能填充到不支持它的瀏覽器中是不可能的。沒有抽象層可以改變修改舊版瀏覽器中的URL會觸發頁面加載的事實。但是,通過在實現瀏覽器的pushState中使用pushState,然後在舊版瀏覽器中使用基於散列的方法,您可以擁有兩全其美的功能。

本傑明·盧普頓(Benjamin Lupton)組建了一個優秀的跨瀏覽器庫,以平滑與保持客戶端歷史記錄一起出現的各種各樣的怪癖和不一致性。他的圖書館涵蓋從Internet Explorer 6到最新版Chrome的所有瀏覽器。使用庫很簡單。它有一個緊跟HTML5自己的pushState語法的語法:

1     // This changes the URL to /state1 in HTML5 browsers, and changes it to

2     // /#/state1 in older browsers.

3     History.pushState(null, 'State 1','state1');

4     // Same as before, but /state2 and/#/state2.

5     History.pushState(null, 'State 2','state2');

history.js並沒有提供HTML5 popstate事件的確切副本,而是包含了各種適配器來處理這些庫中的事件處理系統。例如,使用jQuery適配器,您可以將事件處理程序綁定到history.js statechange事件,如下所示:

1     History.Adapter.bind(window,'statechange',function(){

2       // Get the newhistory state from history.js.

3       var state =History.getState();

4       // Write the URLwe’ve navigated to on the console.

5       console.log(state.url);

6     });

這個statechange事件處理程序在瀏覽器瀏覽通過history.js pushState方法持續存在的歷史點時觸發。無論是在原生支持pushState的HTML5瀏覽器中,還是在僅支持基於散列的URL更改的舊版瀏覽器中,監視此單一事件都會捕獲任何活動。

把它用於真實世界的應用程序很容易。您大概可以想象將它與AJAX驅動的網格分頁和排序結合使用,甚至可以用於整個網站(例如Gmail或Twitter)的導航,而無需訴諸於那些普遍討厭的散列網址和重定向。

用pushScissors運行

使用pushState時需要注意的一件事是,您必須注意,您的服務器將正確響應您在客戶端使用的每個URL。由於構建客戶端URL很容易,服務器將以404或500錯誤(例如,/ undefined)響應,因此確保將服務器端路由或URL重寫配置爲儘可能優雅地處理意外的URL。例如,如果您在/ report上有多頁報表,並且每個頁面上的pushState驅動的URL爲/ report / 2,/ report / 3等,則應確保您的服務器端代碼正常響應/ report / undefined等網址。

不太理想的替代方法是在pushState地址更新中使用查詢字符串URL片段,例如/ report?page = 2和/ report?page = 3。由此產生的URL看起來不太好,但它們至少不太可能導致404錯誤。  

從這往哪兒走

本文只是抓住了HTML5 polyfills生態系統的表面。有一些活躍的項目爲SVG和canvas圖形,HTML5視頻,ECMAScript 5甚至WebWorkers等功能提供跨瀏覽器支持。如果您有興趣瞭解更多關於這些項目的信息,請參閱以下簡要說明以及其中許多內容的鏈接:https//github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills



原文鏈接:http://msdn.microsoft.com/en-us/magazine/jj131727.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章