打造您的 HTML5 网站

简介

针对移动网站进行开发是当今的热门话题。今年,智能手机的销量首次超过了个人电脑。越来越多的用户使用移动设备浏览网页,这意味着,对于开发者来说,针对移动浏览器优化网站变得越来越重要。

对于许多开发者来说,“移动”战场仍然是一片未知的领域。 许多人现有的旧版网站完全忽略了移动用户。 相反,该网站主要针对桌面浏览进行了设计,在移动浏览器中表现不佳。本网站 (html5rocks.com) 也不例外。在发布之初,我们在移动版网站上投入的资源很少。

创建适合移动设备的 html5rocks.com

作为一项练习,我认为将 html5rocks(一个现有的 HTML5 网站)改造成适合移动设备的版本会很有趣。我主要关心的是,针对智能手机进行开发所需的工作量是否最少。我的练习目标不是创建一个全新的移动网站并维护两个代码库。这需要花费很长时间,而且是非常浪费时间的。我们已经定义了网站的结构(标记)。我们查看了外观和风格 (CSS)。核心功能 (JS)。我的意思是,很多网站都面临着同样的问题。

本文将介绍我们如何创建针对 Android 和 iOS 设备进行了优化的 html5rocks 移动版。只需在支持其中一种操作系统的设备上加载 html5rocks.com,即可看到差异。没有重定向到 m.html5rocks.com 或其他此类恶意网站。您可以直接使用 html5rocks,还可以获得额外的好处:在移动设备上呈现效果出色且运行良好。

桌面版 html5rocks.com 移动版 html5rocks.com
桌面版 (html5rocks.com)(左)和移动版 (html5rocks.com)

CSS 媒体查询

HTML4 和 CSS2 已经支持与媒体相关的样式表一段时间了。例如:

<link rel="stylesheet" media="print" href="printer.css">

会定位到打印设备,并在打印页面内容时为其提供特定样式。 CSS3 进一步完善了媒体类型的概念,并通过媒体查询增强了其功能。媒体查询允许更精确地标记样式表,从而扩大了媒体类型的实用性。这样,您无需更改内容本身,即可针对特定范围的输出设备自定义内容的呈现方式。这非常适合需要修改的现有布局!

您可以在外部样式表的 media 属性中使用媒体查询来定位屏幕宽度、设备宽度、屏幕方向等。如需查看完整列表,请参阅 W3C 媒体查询规范

定位屏幕尺寸

在以下示例中,phone.css 适用于浏览器认为是“手持设备”的设备,或屏幕宽度小于等于 320 像素的设备。

 <link rel='stylesheet'
  media='handheld, only screen and (max-device-width: 320px)' href='phone.css'>

如果在媒体查询前面加上“only”关键字,则会导致不符合 CSS3 标准的浏览器忽略该规则。

以下代码会定位到 641 像素到 800 像素之间的屏幕尺寸:

 <link rel='stylesheet'
  media='only screen and (min-width: 641px) and (max-width: 800px)' href='ipad.css'>

媒体查询也可以显示在内嵌 <style> 标记中。以下代码定位到纵向模式下的 all 媒体类型:

 <style>
  @media only all and (orientation: portrait) { ... }
 </style>

media="handheld"

我们需要暂停一下,聊一聊 media="handheld"。事实上,Android 和 iOS 会忽略 media="handheld"。该声明声称,用户将错过以 media="screen" 为目标平台的样式表提供的高端内容,并且开发者不太可能维护质量较低的 media="handheld" 版本。因此,作为其“完整网络”座右铭的一部分,大多数现代智能手机浏览器都会忽略手持样式表。

理想情况下,应使用此功能定位移动设备,但各种浏览器实现此功能的方式有所不同:

  • 有些设备只读取手持设备样式表。
  • 有些设备仅读取手持设备样式表(如果有),否则默认使用屏幕样式表。
  • 有些设备会同时读取手持设备样式表和屏幕样式表。
  • 有些只读取屏幕样式表。

Opera Mini 不会忽略 media="handheld"。让 Windows Mobile 识别 media="handheld" 的诀窍是将屏幕样式表的媒体属性值转换为大写形式:

 <!-- media="handheld" trick for Windows Mobile -->
 <link rel="stylesheet" href="screen.css" media="Screen">
 <link rel="stylesheet" href="mobile.css" media="handheld">

html5rocks 是如何使用媒体查询的

移动版 html5rocks 中大量使用了媒体查询。借助这些工具,我们无需对 Django 模板标记进行重大更改,即可调整布局… 真是太棒了!此外,他们对各种浏览器的支持非常出色。

在每个网页的 <head> 中,您会看到以下样式表:

 <link rel='stylesheet'
  media='all' href='/static/css/base.min.css' />
 <link rel='stylesheet'
  media='only screen and (max-width: 800px)' href='/static/css/mobile.min.css' />

base.css 一直是 html5rocks.com 的主要外观和风格,但现在,我们将针对宽度小于 800 像素的屏幕应用新样式 (mobile.css)。其媒体查询涵盖智能手机(约 320 像素)和 iPad(约 768 像素)。 效果:我们会逐步替换 base.css 中的样式(仅在必要时),以便在移动设备上获得更好的外观。

mobile.css 强制执行的一些样式更改:

  • 减少网站上的额外空白/内边距。屏幕较小意味着空间非常宝贵!
  • 移除了 :hover 状态。它们从未在触控设备上出现过。
  • 将布局调整为单列。稍后我们会详细介绍这部分内容。
  • 移除了网站主容器 div 周围的 box-shadow。较大的边框阴影会降低页面性能。
  • 使用了 CSS 弹性框模型 box-ordinal-group 来更改首页上每个部分的排序。 您会发现,“按主要 HTML5 功能组学习”位于网页版首页上的“教程”部分之前,但位于移动版首页上的“教程”部分之后。这种排序对移动设备而言更有意义,且无需更改标记。CSS flexbox 真棒!
  • 移除了 opacity 更改。更改 Alpha 值会降低移动设备上的性能。

移动元标记

Mobile WebKit 支持一些实用功能,可让用户在某些设备上获得更好的浏览体验。

视口设置

第一个元设置(也是您最常用的设置)是视口属性。设置视口可告知浏览器内容应如何适应设备屏幕,并告知浏览器网站已针对移动设备进行了优化。例如:

 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">

告知浏览器将视口设为设备的宽度,初始缩放比例为 1。此示例还支持缩放,这对于网站来说可能很有用,但对于 Web 应用来说却不适用。我们可以使用 user-scalable=no 阻止缩放,或将缩放限制在一定水平:

 <meta name=viewport
  content="width=device-width, initial-scale=1.0, minimum-scale=0.5 maximum-scale=1.0">

Android 扩展了 viewport meta 标记,允许开发者指定网站是针对哪种屏幕分辨率开发的:

 <meta name="viewport" content="target-densitydpi=device-dpi">

target-densitydpi 的可能值包括 device-dpihigh-dpimedium-dpilow-dpi

如果您要针对不同的屏幕密度修改网页,请使用 -webkit-device-pixel-ratio CSS 媒体查询和/或 JavaScript 中的 window.devicePixelRatio 属性,然后将 target-densitydpi 元属性设为 device-dpi。这样,Android 便不会在网页中执行缩放,您可以通过 CSS 和 JavaScript 为每种密度进行必要的调整。

如需详细了解如何以设备分辨率为目标平台,请参阅 Android 的 WebView 文档

全屏浏览

另外还有两个 iOS 专用的元值。apple-mobile-web-app-capableapple-mobile-web-app-status-bar-style 将在类似于应用的全屏模式下呈现网页内容,并使状态栏变为半透明:

 <meta name="apple-mobile-web-app-capable" content="yes">
 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

如需详细了解所有可用元选项,请参阅 Safari 参考文档

主屏幕图标

iOS 和 Android 设备还接受 rel="apple-touch-icon"(iOS)和 rel="apple-touch-icon-precomposed"(Android)作为链接。当用户将您的网站添加为书签时,这些图标会在用户的主屏幕上显示一个类似应用的醒目图标:

 <link rel="apple-touch-icon"
      href="/static/images/identity/HTML5_Badge_64.png" />
 <link rel="apple-touch-icon-precomposed"
      href="/static/images/identity/HTML5_Badge_64.png" />

html5rocks 如何使用移动设备元标记

将所有内容整合在一起,下面是 html5rocks 的 <head> 部分的代码段:

 <head>
  ...
   <meta name="viewport"
        content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />

   <link rel="apple-touch-icon"
        href="/static/images/identity/HTML5_Badge_64.png" />
   <link rel="apple-touch-icon-precomposed"
        href="/static/images/identity/HTML5_Badge_64.png" />
  ...
 </head>

垂直布局

在小屏幕上,垂直滚动比水平滚动更方便。 对于移动设备,最好将内容保持单列竖屏布局。 对于 html5rocks,我们使用了 CSS3 媒体查询来创建此类布局。同样,无需更改标记。

教程索引。 教程。 HTML5 功能页面。 作者个人资料页面。
整个网站采用单列垂直布局。

移动优化

我们进行的大多数优化都是应首先完成的事情。例如减少网络请求数量、JS/CSS 压缩、gzip 处理(App Engine 上免费提供)以及最大限度地减少 DOM 操作。这些技巧是常见的最佳实践,但在匆忙发布网站时,有时会被忽略。

自动隐藏地址栏

移动浏览器的屏幕空间不如桌面浏览器。更糟糕的是,在不同的平台上,您有时会在屏幕顶部看到一个巨大的地址栏,即使网页加载完毕也是如此。

解决此问题的一个简单方法是使用 JavaScript 滚动页面。即使只向下移动一个像素,也能解决恼人的地址栏问题。为了在 html5rocks 上强制隐藏地址栏,我将 onload 事件处理脚本附加到 window 对象,并将页面垂直滚动了 1 像素:

地址栏。
丑陋的地址栏占据了屏幕空间。
  // Hides mobile browser's address bar when page is done loading.
  window.addEventListener('load', function(e) {
    setTimeout(function() { window.scrollTo(0, 1); }, 1);
  }, false);

我们还将此监听器封装为 is_mobile 模板变量,因为桌面设备不需要它。

减少网络请求,节省带宽

众所周知,减少 HTTP 请求数量可以显著提升性能。移动设备进一步限制了浏览器可以建立的并发连接数量,因此,减少这些多余请求对移动网站的益处会更大。此外,由于手机的带宽通常有限,因此尽可能减少字节数至关重要。您可能在让用户付费!

以下是我们为最大限度地减少网络请求并降低 html5rocks 上的带宽而采取的一些方法:

  • 移除 iframe - iframe 速度缓慢!大量延迟来自教程页面上的第三方分享 widget(Buzz、Google Friend Connect、Twitter、Facebook)。这些 API 是通过 <script> 代码添加的,并会创建会降低网页速度的 iframe。移动版中移除了微件。

  • display:none - 在某些情况下,如果标记不适合移动版个人资料,我们会隐藏该标记。首页顶部的四个圆角框就是一个很好的例子:

首页上的框按钮。
首页上的方框按钮。

移动网站上缺少这些链接。请务必注意,即使其容器已使用 display:none 隐藏,浏览器仍会针对每个图标发出请求。因此,仅隐藏这些按钮是不够的。这不仅会浪费带宽,而且用户甚至不会看到浪费带宽带来的好处!解决方案是在 Django 模板中创建一个“is_mobile”布尔值,以便有条件地省略 HTML 部分。当用户在智能设备上查看网站时,系统会忽略这些按钮。

  • 应用缓存 - 这不仅为我们提供了离线支持,还加快了启动速度。

  • CSS/JS 压缩 - 我们使用 YUI 压缩工具(而非 Closure 编译器),主要是因为它同时处理 CSS 和 JS。我们遇到了一个问题,即内嵌媒体查询(出现在样式表内的媒体查询)在 YUI 压缩程序 2.4.2 中出错(请参阅此问题)。使用 YUI Compressor 2.4.4 或更高版本可解决此问题。

  • 尽可能使用 CSS 图片精灵。

  • 使用 pngcrush 进行图片压缩。

  • 针对小图片使用了 dataURI。Base64 编码会使图片增加约 30% 以上的大小,但可以减少网络请求。

  • 使用单个脚本代码自动加载 Google 自定义搜索,而不是使用 google.load() 动态加载。后者会发出额外的请求。

<script src="//www.google.com/jsapi?autoload={"modules":[{"name":"search","version":"1"}]}"> </script>
  • 我们的代码美化打印程序和 Modernizr 会包含在每个网页中,即使它们从未使用过也是如此。Modernizr 非常棒,但它会在每次加载时运行一系列测试。其中一些测试会对 DOM 进行代价高昂的修改并降低网页加载速度。现在,我们只在实际需要的网页上添加这些库-2 个请求 :)

其他性能调整:

  • 将所有 JS 移至页面底部(如有可能)。
  • 移除了内嵌 <style> 标记。
  • 缓存 DOM 查询并尽量减少 DOM 操作 - 每次您触摸 DOM 时,浏览器都会执行重新流式传输。在移动设备上,重新流布局的开销更高。
  • 将浪费的客户端代码分流到服务器。具体而言,用于设置当前页面的导航样式的检查:js var lis = document.querySelectorAll('header nav li'); var i = lis.length; while (i--) { var a = lis[i].querySelector('a'); var section = a.getAttribute("data-section"); if (new RegExp(section).test(document.location.href)) { a.className = 'current'; } }
  • 固定宽度的元素已替换为自适应尺寸的 width:100%width:auto

应用缓存

移动版 html5rocks 使用应用缓存加快初始加载速度,并允许用户离线阅读内容。

在您的网站上实现 AppCache 时,切勿缓存清单文件(在清单文件本身中显式缓存或因使用密集型缓存控制标头隐式缓存)。如果清单被浏览器缓存,调试将会非常困难。iOS 和 Android 在缓存此文件方面做得特别出色,但不像桌面浏览器那样提供方便的缓存刷新方式。

为了防止对我们的网站进行上述缓存,我们首先将 App Engine 设置为永不缓存清单文件:

- url: /(.*\.(appcache|manifest))
  static_files: \1
  mime_type: text/cache-manifest
  upload: (.*\.(appcache|manifest))
  expiration: "0s"

其次,我们使用 JS API 在新清单下载完成后通知用户。系统会提示用户刷新页面:

window.applicationCache.addEventListener('updateready', function(e) {
  if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    window.applicationCache.swapCache();
    if (confirm('A new version of this site is available. Load it?')) {
      window.location.reload();
    }
  }
}, false);

为节省网络流量,请保持清单简单。也就是说,请勿列出您网站上的每个网页。只需列出重要的图片、CSS 和 JavaScript 文件即可。您最不想做的就是强制移动浏览器在每次更新 appcache 时下载大量资源。请记住,当用户访问时,浏览器会隐式缓存 HTML 页面(并且该页面包含 <html manifest="..."> 属性)。