如今HTML5:经过Polyfills得到更多

如今HTML5:经过Polyfills得到更多

戴夫沃德 | 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

什么使Polyfill?

有关我正在谈论的具体示例,请查看json2.js。具体来讲,这是JSON.parse实现中的第一行代码:web

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,同时仍保持与旧版浏览器的兼容性。json

HTML5的新语义元素

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.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

相关文章
相关标签/搜索