使用 HTML5 构建应用程序

浏览器不应落伍:HTML5 采用策略

Brandon Satrom

下载代码示例

HTML5 有很多令人兴奋的功能。借助新的标记、CSS 功能和 JavaScript API,Web 上可能出现的内容类型将急剧增加。与此同时,浏览器供应商的优越感也在稳定增长,激动人心的功能表列表几乎每天都在增长。从每夜构建到开发渠道版本和定期平台预览,浏览器正在飞速变革,而世界各地的 Web 开发人员也在经历一段令人振奋的旅程。

虽然所有开发和浏览器社区都在极力宣传 HTML5,但是网络上绝大多数用户使用的浏览器及其版本都不比我们所使用的更新、更高。如果您在一个拥有大量用户的大型开发工作室或企业中担任 Web 开发人员,那么您可能早已深谙此道。即使您在一个通过 Web 提供某些服务的小型工作室或初创企业工作,您也可能需要花费很多时间来确保您的网站适应尽可能多的浏览器或浏览器版本。

鉴于这一现实,很容易看出问题不在于 HTML5 本身是否可以立即使用,而在于 是否已经做好准备。例如,您使用某些全新的语义标记(如 <header> 和 <article>)创建了一个页面,添加了一些新的 CSS 功能(如 border-radius 和 box-shadow),甚至添加了 <canvas> 元素以便在您的页面上画上 HTML5 标志。

在 Internet Explorer 9、Firefox 4 及更高版本或 Google Chrome 等较新的浏览器中,呈现出的内容将如图 1 所示。但是如果您尝试将这一页面加载到 Internet Explorer 8 或更低版本中,您看到的内容则更像图 2 所示:一个支离破碎的页面。

Semantic Page with Styles and an HTML5 <canvas> Element, Rendered in Internet Explorer 9
图 1 一个具有样式和 HTML5 <canvas> 元素的语义页面,呈现在 Internet Explorer 9

The Same Semantic Page, Rendered in Internet Explorer 8 with No Styles and No <canvas>
图 2 相同的语义页面呈现在 Internet Explorer 8 中时没有样式和 <canvas>

如果您看到的全是 HTML5 的强大功能,并且在拥有这样一种体验之后告诉自己最好的办法就是等待,我非常理解。很容易会得出这样一个结论:无论是否做好准备,HTML5 还没有完全准备好供您或您的用户使用。

在您做出等到 2022 年的某一天再来看看 HTML5 这一决定之前,我希望您读完这篇文章的其余部分。本月,我的目标是为您提供切实可行的策略,告诉您如何在今天 就采用 HTML5 技术,而不会出现图 2 所示的非妥善降级效果。本文将讨论:

  1. 功能检测与用户代理 (UA) 探查
  2. 使用 JavaScript 填充代码
  3. 妥善降级

这三个主题将告诉您构建适用于各种浏览器的网站所需知道的大量信息。当我们结束时,您将拥有一套可靠的采用 HTML5 技术的策略,并且信心满满,毫不拖延。您还会获得一些工具,可帮助您针对较新的浏览器逐步增强站点,同时针对较旧的浏览器妥善降级。

功能检测的重要性

为了在不同的浏览器中提供稳定、一致的体验,开发人员通常需要对用户的浏览器有所了解。以前,一般通过类似以下的 JavaScript 代码来确定这些信息:

var userAgent = navigator.userAgent;
 
if (userAgent.indexOf('MSIE') >= 0) {
  console.log("Hello, IE user");
} else if (userAgent.indexOf('Firefox') >= 0) {
  console.log("Hello, Firefox user");
} else if (userAgent.indexOf('Chrome') >= 0) {
  console.log("Hello, Chrome user");
}

这种技术称为 UA 探查,广泛用于确定正在请求您的页面的浏览器类型。 其中的逻辑是,当您知道用户的浏览器(例如 Internet Explorer 7)时,您可以在运行时决定启用或禁用您站点的哪些功能。 UA 探查相当于可以和浏览器交谈:“你是谁?”(要深入了解 UA 探查和其他检测技术,请参阅 bit.ly/mlgHHY。)

此方法的问题是浏览器可能会撒谎。 UA 字符串是一小段可由用户配置的信息,并不一定会准确告诉您相关浏览器的信息。 此外,因为这项技术已经广泛使用,许多浏览器供应商都在他们自己的 UA 字符串中添加额外内容作为一种欺骗脚本的方式,使脚本对正在使用的浏览器做出错误的判断,从而绕过检测。 现在有些浏览器甚至包含一种机制,只需用户点击几下即可更改他们的 UA 字符串。

然而,UA 探查的目标从来都不是要了解用户的浏览器和版本。 它的目标当然不会是为您提供一种途径,让您在不喜欢用户所使用的浏览器时告诉用户去“下载另一个浏览器”— 虽然这种技术确实有人在用。 用户确实可以选择他们所使用的浏览器,但我们作为开发人员的责任是提供最可靠、最一致的体验,而不是把我们对浏览器的偏好强加给他们。 UA 探查的目标始终是帮助您准确了解用户的当前浏览器中可供使用的功能或特性。 了解浏览器本身只是为了获得该信息。

今天,UA 探查已经有了多种替代方法,其中一种越来越流行的方法(部分归功于 jQuery 和 Modernizr)名为对象或功能检测。 这些术语大多是可以互换的,但我在本文中会一直使用“功能检测”。

功能检测的目标是确定用户当前的浏览器是否支持给定的功能或特性。 如果说 UA 探查像是在问浏览器“你是谁”,那么功能检测就像在问浏览器“你有哪些功能”— 这是一个更直接的问题,也是一种更可靠的方法,可以帮助您决定是否为用户提供需要满足特定条件才能使用的功能。 只要功能检测脚本的实施正确无误,用户和浏览器就很难伪造或错误地报告功能支持。

手动功能检测

那么,相对于 UA 探查的示例,功能检测是什么样的呢? 要回答这个问题,让我们看看如何修复在 Internet Explorer 8 而不是 Internet Explorer 9 中查看 HTML5 页面时出现的问题(如图 1 所示)。 此页面的标记如图 3 所示。

图 3 带有新语义 HTML5 标记的页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>My Awesome Site</title>
  <style>
    body { font-size: 16px; font-family: arial,helvetica,clean,sans-serif;  }
    header h1 { font-size: 36px; margin-bottom: 25px; }
    article 
    {
      background: lightblue;
      margin-bottom: 10px;
      padding: 5px;
      border-radius: 10px;
      box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5);
    }
    article h1 { font-size: 12px; }
  </style>
</head>
<body>
  <header><h1>My Awesome Site</h1></header>
  <article>
    <header><h1>An Article</h1></header>
    <p>Isn't this awesome?</p>
  </article>
  <canvas width="250" height="500"></canvas>
</body>
<script src="../js/html5CanvasLogo.js" type="text/javascript"></script>
</html>

Internet Explorer 9 和 Internet Explorer 8 之间的差别非常大,如图 1图 2 所示。 对于初学者来说,我的页面完全没有样式,就像此页面的 CSS 根本不存在一样。 此外,页面的底部还丢失了花哨的 HTML5 盾牌标志。 所有这些问题都可以轻松修复,而功能检测就是找出这些问题的第一步。

造成这两个问题的原因很简单:对于 Internet Explorer 8 而言,<header>、<article> 和 <canvas> 都不是有效的 HTML 元素,因此我不能使用它们。 要解决 <canvas> 问题,如果不使用 UA 探查判断正在使用的浏览器/版本,我想通过 JavaScript 询问浏览器是否支持 <canvas> 元素及其 JavaScript API。 我对 canvas 执行的功能检测如下所示:

!!document.createElement('canvas').getContext

此语句做了几件事。 首先,它使用双重否定 (!!) 将未定义的值强制转换为显式的 false。 然后,手动创建一个新的 canvas 元素并将其附加到 DOM。 最后,它调用 getContext,这种新函数可由 <canvas> 元素使用,是一种通过 JavaScript 来操控 Canvas API 的方法。 如果我正在使用 Internet Explorer 9,这一语句将返回 true。 如果我正在使用 Internet Explorer 8,getContext 将返回“undefined”,这样我的双重否定将强制转换为 false。

这是最基本的功能检测。 借助这条语句以及其他类似语句,我现在有了一种更可靠的方法来查询浏览器支持的功能。 有关手动功能检测的详细信息,请访问 diveintohtml5.info/everything.html

使用 Modernizr 进行功能检测

手动功能检测相对于 UA 探查肯定是一项巨大的改善,但这种方法仍然需要您完成很多繁重的任务:您要检测功能的可用性;如果功能不存在,还要决定该怎么处理。 Canvas 元素是一个非常简单的示例,它只需要一行代码,而您想要检测的每一项功能不会都是这样,所有浏览器上的检测代码也不会都相同。 例如,检测我以前使用过的 CSS3 模块支持(border-radius 和 box-shadow)就可能会更复杂。

令人欣慰的是,Modernizr (modernizr.com) 提供了一种更好的方法。 Modernizr 是一个 JavaScript 库,用于“…检测是否可以使用下一代 Web 技术(也就是来源于 HTML5 和 CSS3 规范的功能)的本地实现。” 在您的页面中添加对 Modernizr 的引用,将提供四个主要功能:

  1. 一个包含所有受支持的功能的完整列表,您可以将其巧妙地添加到您的标记中,用于启用有条件的 CSS 定义。
  2. 一个 JavaScript 对象,用于帮助实现基于脚本的功能检测。
  3. 在运行时添加到 DOM 中的全部新 HTML5 标记,这将有益于 Internet Explorer 8 和更低版本的 Internet Explorer 浏览器(马上将详细介绍)。
  4. 一个脚本加载程序,用于将填充代码按条件加载到您的页面中。

本文不再深入讨论第一项,但我希望您能到 modernizr.com 网站查看与此功能及其他功能相关的文档。

第二项功能有助于将这行代码:

!!document.createElement('canvas').getContext

转换为这行代码:

Modernizr.canvas

它将返回一个 Boolean,指示页面是否支持 canvas 元素。 与自行编写功能检测相比,使用 Modernizr 的最大优点在于 Modernizr 是经过充分测试、稳定可靠且被广泛使用的库,它可以帮助您完成繁重的任务。 Twitter、Google、Microsoft 和其他不计其数的网站都在使用 Modernizr,您也可以使用。 随着 ASP.NET MVC 3 Tools Update(发布于 2011 年 4 月)的发布,Microsoft 甚至在全新的 ASP.NET MVC 应用程序中附带了 Modernizr。 当然,我目前介绍的仅仅是检测是否支持 <canvas> 元素, 还丝毫没有谈到下一步该怎么做。 通过功能检测了解某一功能是否适用于某一浏览器之后,常见的下一步是创建一些条件逻辑,以便在功能不存在时阻止执行某些代码或执行一个替代方案,与此类似:

 

if (Modernizr.canvas) {
  // Execute canvas code here.
}

根据浏览器是否存在某些功能而为您的站点添加功能,这种方式被称为“逐步增强”,因为对于功能更强的浏览器,您可以改善其体验。 这种方式的另一面是“妥善降级”,即在缺少某些功能时,不会造成浏览器错误或失败,而是为用户提供一些降级的功能或其他替代功能。 对于旧式浏览器,妥善降级不一定是您的默认选择。 在很多情况下,它可能也不是您的最佳选择。 相反,在 Modernizr 的帮助下,您经常可以使用一种可用的浏览器填充代码,将类似 HTML5 的功能添加到不支持这些功能的浏览器中。

填充代码是什么?

据 Modernizr 网站所说,填充代码是“为旧式浏览器复制 API 的 JavaScript 填充程序。”“标准 API”是指某种具体的 HTML5 技术或功能,如 canvas。 “JavaScript 填充程序”意味着在不支持某些 API 的浏览器中,您可以动态加载 JavaScript 代码或库来模拟这些 API。 例如,Geolocation 填充代码可以将全球地理位置对象添加到导航器对象中,还可以添加 getCurrentPosition 函数和“cords”回调对象,所有这些都符合 World Wide Web Consortium (W3C) Geolocation API 中的定义。 因为填充代码模拟了标准 API,您可以采用面向未来的方式针对该 API 为所有浏览器进行开发,以便在支持达到临界程度时删除填充代码。 不需要额外的工作。

通过在我的页面上添加对 Modernizr 的引用,我将立即得到填充代码的好处,如图 3 所示。 此页面呈现的内容没有样式,因为 Internet Explorer 8 无法识别 <article> 和 <header> 这样的标签。 因为无法识别它们,所以它不会将它们添加到 DOM 中,而 DOM 决定了 CSS 如何选择要设置样式的元素。

当我在页面上添加 <script> 标记以及对 Modernizr 的引用后,就会获得带样式的页面,如图 4 所示。 我获得这一好处,是因为 Modernizr 使用 JavaScript (document.CreateElement('nav')) 手动将所有 HTML5 标记添加到 DOM 中,这样就允许 CSS 选择这些标记并设置其样式。

An HTML5 Page in Internet Explorer 8, with the Help of Modernizr
图 4 借助 Modernizr,Internet Explorer 8 中呈现的 HTML5 页面

除了用于在 Internet Explorer 中添加新的 HTML5 元素以外,Modernizr 库不提供其他任何现成的填充代码。 这些都必须由您自己提供,而无论它们是来自您自己的脚本,还是来自 Modernizr 网站上记录的不断增长的选项列表。 自 2.0 版开始,Modernizr 提供一个条件脚本加载程序(基于 yepnope.js — yepnopejs.com),帮助您仅在需要时异步下载填充代码库。 将 Modernizr 与一个或多个填充代码库配合使用来提供您所需的功能,这是一种强大的组合方式。

使用填充代码模拟 HTML5 功能

在 canvas 示例中,您可以借助 Modernizr 和名为 excanvas 的 JavaScript 库,为 Internet Explorer 8 及更低版本提供填充代码支持,从而在 API 级向 Internet Explorer 6、Internet Explorer 7 和 Internet Explorer 8 中添加 canvas。 您可以从 bit.ly/bSgyNR 网站下载 excanvas,并且在您将它添加到脚本文件夹中后,您就可以在页面上的脚本模块中添加一些代码,如图 5 所示。

图 5 使用 Modernizr 来提供 canvas 填充代码支持

 

Modernizr.load({
  test: Modernizr.canvas,
  nope: '../js/excanvas.js',
  complete: function () {
    Modernizr.load('../js/html5CanvasLogo.js');
  }
}]);

在此,我将使用 Modernizr 脚本加载程序来指出三点:

  1. 要测试的 Boolean 表达式
  2. 指向要在表达式的值为 false 时加载的脚本的路径
  3. 要在检查或脚本加载完成后运行的回调

在 canvas 上下文中,这是我为应用程序添加一些智能和填充代码所需要做的全部工作。 Modernizr 为不支持 canvas 的浏览器异步加载 excanvas.js,然后加载我的脚本库,以便在页面上画上 HTML5 标志。

我们来看另一个示例,再次强调 Modernizr 的价值。 如果您注重细节,可能已经注意到图 4 中展示的网站样式可能与 Internet Explorer 9 中所呈现的原始页面(如图 1 所示)不完全相同。 在 Internet Explorer 8 中所看到的页面丢失了阴影框和圆角,而我推出的完美网站中不能丢失其中任何一个,因此我们将再次寻求 Modernizr 的帮助。

和 canvas 一样,Modernizr 可以告诉我 CSS3 模块不受支持,但要由我来决定是否提供库,以便为其提供填充代码。 幸运的是,一个名为 PIE (css3pie.com) 的库可以在单个库中同时提供这两个模块。

为了添加对 border-radius 和 box-shadow 的支持,我可以在下载 PIE 之后将图 6 中的代码添加到脚本中。 这次,我将进行测试,以查看 border-radius box-shadow 模块是否受支持(而不是假定两者都受支持或都不受支持);如果有一个不受支持,则动态加载 PIE.js。 加载完 PIE 之后,我将执行一条 jQuery 以全部选中我的 <article> 标记并调用 PIE.attach 函数,以便支持已经在我的 CSS 中定义的 border-radius 和 box-shadow 样式。 最终结果如图 7 所示。

图 6 使用 Modernizr 和 PIE 添加 CSS3 支持

Modernizr.load({
  test: Modernizr.borderradius || Modernizr.boxshadow,
  nope: '../js/PIE.js',
  callback: function () {
    $('article').each(function () {
      PIE.attach(this);
    });
  }
});

CSS3 Support with Modernizr and PIE
图 7 通过 Modernizr 和 PIE 实现的 CSS3 支持

使用填充代码帮助实现妥善降级

除了使用此处讨论的填充代码技术以外,当您想要让应用程序妥善降级时,也可以使用 Modernizr 来提供帮助,而不是使用另一个库中的填充代码。

假定我在网页上拥有一个 Bing Maps 控件,并且我想使用 Geolocation — 这一点我将在以后的文章中深入介绍 — 查找用户的当前位置并在地图控件上将该位置设为图钉。

虽然所有浏览器的最新版本都支持 Geolocation,但是较低版本的浏览器却不支持。 而仅通过 JavaScript 来提供完整的 Geolocation API 也比较麻烦,虽然 Geolocation 的填充代码确实存在,但我已经决定改为让我的应用程序妥善降级。 在用户的浏览器不支持 Geolocation 的情况下,我可以提供一个表单让她手动输入位置,然后我再用它在地图上进行定位并显示图钉。

借助 Modernizr,可以轻松加载对我创建的其中一个脚本的调用,如图 8 所示。 在本示例中,我测试的是 Modernizr.geolocation 属性。 如果为 true(“是”),我将加载我的 fullGeolocation.js 脚本,这将使用 Geolocation API 来查找我的位置(经过我的允许)并将其放到地图上,如图 9 所示。 反之,如果测试为 false(“否”),我将加载一个回退脚本,在我的页面上显示一个地址表单。 当用户提交该表单时,我将以提供的地址为中心显示地图并在该位置显示图钉,如图 10 所示。 通过这种方式,我的页面将为新版浏览器提供完美体验,而为旧式浏览器妥善降级到一个合理的替代方案。

图 8 使用 Modernizr 提供妥善降级

Modernizr.load({
  test: Modernizr.geolocation,
  yep: '../js/fullGeolocation.js',
  nope: '../js/geolocationFallback.js'
});

Mapping with Geolocation
图 9 通过 Geolocation 进行地图定位

Providing Geolocation Fallback Support
图 10 提供 Geolocation 回退支持

您很容易就能看到 HTML5 的一些先进功能,而在面对仍然使用旧版浏览器的庞大用户群时,决定您的网站还没有准备好为他们提供服务。 但是已经存在可用的出色解决方案,不仅能帮助实现妥善降级,还能为旧版浏览器提供新版浏览器的功能,使您的用户现在就能体验 HTML5。 在本文中,您已经看到借助功能检测、Modernizr 和填充代码,可以毫不迟疑地采用 HTML5,以便适应不断增长的使用新版浏览器的用户群,同时也不会让其他用户落伍。

Brandon Satrom 是 Microsoft 在德克萨斯州奥斯汀市的开发推广人员。他的博客地址为 UserInexperience.com,还可以通过 Twitter 地址 twitter.com/BrandonSatrom 与他联系。

衷心感谢以下技术专家对本文进行了审阅:Damian EdwardsScott Hunter  Clark Sell