戴夫沃德 | 2012年5月17日html
利用 HTML5 來搭建網站和應用多是一項艱鉅的任務。儘管如今愈來愈多的現代瀏覽器正在更多的支持Html5新特性,但實際上只有不多部分人可以幸運的只須要爲這些最新的瀏覽器編寫代碼。做爲一個專業的開發者,你必需要花不少精力來調整不自由的空間排版和實現承諾過的特性以及面對如今的現實狀況,這些都是由於瀏覽器的碎片化。好消息是 IE 9 和 10 都已經支持HTML5 了,用戶能夠拋棄舊版的 Internet Explorer 瀏覽器了,不過對於開發者而言他們還須要考慮支持舊版的瀏覽器。html5
可是,這並不意味着您不得不放棄在短時間內支持HTML5。就像網站有技巧支持多種屏幕尺寸和不一樣級別的CSS功能之類的差別同樣,也能夠實現使人驚訝的強大的跨瀏覽器HTML5支持。儘管較舊的瀏覽器缺乏許多HTML5的新API,但JavaScript是一種很是靈活的語言,而且提供了追溯性地添加新功能的機會,當它們不是本地存在時。jquery
跳躍到HTML5最讓人頭痛的問題是,咱們大多數人別無選擇,只能支持各類對最有用的新API幾乎或不支持的舊瀏覽器。採用新的Web技術的想法讓人聯想到了跨瀏覽器不一致性,不可維護的分支代碼,瀏覽器嗅探以及其餘一系列問題的惡夢。可是,有一項評估不足的技術能夠徹底緩解HTML5某些新功能的這些問題,而且仍然容許您針對新的API進行開發,就好像您的全部用戶在一晚上之間都升級了瀏覽器:polyfills。git
Polyfilling是Remy Sharp創造的一個術語,用於描述以重複缺失API的方式回填缺失功能的方法。使用這種技術,您能夠編寫特定於應用程序的代碼,而不用擔憂每一個用戶的瀏覽器是否本機實現它。事實上,polyfills不是一種新技術,也不是綁定到HTML5。多年來,咱們一直在使用諸如json2.js,ie7-js之類的polyfills,以及在Internet Explorer中提供透明PNG支持的各類回退。不一樣之處在於去年HTML5 polyfills的擴散。github
有關我正在談論的具體示例,請查看json2.js。具體來講,這是JSON.parse實現中的第一行代碼:web
if(typeof 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,同時仍保持與舊版瀏覽器的兼容性。json
HTML5 中對於polyfil來講最簡單的特性就是設置已經增長了的語義元素,如<article>,<aside>,<header>和<time>。他們中的大多數和<div>,<span>的表現沒有區別,可是它們有本身語義化的意義。由於這些元素是標準通用內置語言(SGML),因此好處就是像 IE6 這樣的舊瀏覽器也可以顯示他們。不過 IE瀏覽器的奇怪之處就是它只應用那些它認可的 CSS 樣式。所以,即便舊的 IE瀏覽器顯示了 HTML5 的新語義元素,可是它仍會忽視那些用戶自定義的樣式。canvas
幸運的是,Sjoerd Visscher 爲 IE 找到了一個簡單的解決方法,John Resig 又讓它發揚光大。在使用任意元素形式的時候調用 document.createElement(),這樣就可讓 IE 瀏覽器運用 CSS 中全部的樣式了。api
例如,在 <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.js和PersistJS提供的全面向後兼容性,以及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驅動更改同步。
儘管操縱瀏覽器的哈希值獲得了很好的支持,但即便超過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,它會很好用。
操做哈希是解決客戶端狀態管理問題的良好開端,但它並不是沒有缺點。劫持合法的瀏覽器導航功能不是最佳選擇,由於基於散列的URL結構可能會致使用戶混淆並與現有的頁內導航發生衝突。
一個更基本的問題是,瀏覽器不會在HTTP請求中包含請求的URL的哈希部分。若是不訪問URL的這一部分,就不可能當即返回與用戶加入書籤,經過電子郵件接收或經過社交分享發現的頁面處於相同狀態的頁面。這致使網站別無選擇,只能在默認的初始狀態下顯示頁面,而後自動觸發一個使人震驚的轉換,直到用戶真正須要的狀態。爲了找到這種影響可用性的證據,除了對Twitter和Gawker Media的「hash bang」從新設計普遍的負面反應以外,您還須要尋找其餘方面。
幸運的是,HTML5還引入了一對更高級的API,可顯着改善客戶端歷史管理狀況。一般簡稱爲pushState,window.history.pushState方法和window.onpopstate事件的組合提供了異步處理瀏覽器地址的整個路徑部分的途徑,而且一樣對哈希以外的導航事件作出反應。
在GitHub上瀏覽項目的源代碼是當前使用pushState最好的真實世界示例之一。因爲使用pushState操縱瀏覽器的地址不會像傳統的地址更改那樣致使整個頁面的刷新,因此GitHub可以在每一個代碼的「頁面」之間提供動畫轉換,同時仍然保留用戶友好的URL哈希或querystrings。
更好的是,若是您將書籤保存到這些URL之一併稍後直接導航到其中,則GitHub可以在第一個請求中當即向您提供正確的內容,由於客戶端URL結構與它們在服務器上使用的內容相匹配。正如我前面提到的,當你使用基於散列的URL時,這樣作是不可能的,由於你的Web服務器永遠不會知道請求的散列部分。
不幸的是,要真正將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)的導航,而無需訴諸於那些廣泛討厭的散列網址和重定向。
使用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