简介
移动网站开发是当下的热门话题。今年,智能手机的销量首次超过了 PC。越来越多的用户使用移动设备浏览网页,这意味着开发者必须针对移动浏览器优化其网站。
对许多开发者来说,“移动”战场仍是一片未知领域。 许多人拥有现有的旧版网站,这些网站完全忽略了移动用户。 相反,该网站主要针对桌面浏览器而设计,在移动浏览器中降级效果不佳。此网站 (html5rocks.com) 也不例外。在发布之初,我们几乎没有在网站的移动版本上投入精力。
创建适合移动设备的 html5rocks.com
作为一项练习,我认为采用 html5rocks(一个现有的 HTML5 网站)并为其增加一个适合移动设备的版本会很有趣。 我主要关注的是针对智能手机进行开发所需的最少工作量。我的练习目标不是创建一个全新的移动网站并维护两个代码库。这会耗费大量时间,造成巨大的时间浪费。我们已经定义了网站的结构(标记)。我们有外观和风格 (CSS)。核心功能 (JS) 已经具备。重点是,许多网站都面临着同样的问题。
本文探讨了我们如何创建针对 Android 和 iOS 设备进行了优化的 html5rocks 移动版。只需在支持其中一种操作系统的设备上加载 html5rocks.com,即可看到差异。 没有重定向到 m.html5rocks.com 或其他类似愚蠢行为。您将获得原汁原味的 html5rocks… 额外的好处是,它在移动设备上看起来很棒,运行也很流畅。
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更改了首页上每个版块的顺序。 您会注意到,在首页上,“LEARN BY MAJOR HTML5 FEATURE GROUPS”(按主要 HTML5 功能组学习)部分位于“TUTORIALS”(教程)部分之前,但在移动版上,该部分位于“TUTORIALS”部分之后。这种顺序更适合移动设备,并且不需要更改标记。 CSS flexbox FTW! - 移除
opacity项更改。更改 Alpha 值会影响移动设备上的性能。
移动元标记
移动 WebKit 支持一些功能,可在某些设备上为用户提供更好的浏览体验。
视口设置
第一个元设置(也是您最常用的设置)是 viewport 属性。 设置视口可告知浏览器内容应如何适应设备屏幕,并告知浏览器网站已针对移动设备进行优化。例如:
<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 扩展了视口元标记,允许开发者指定网站是针对哪种屏幕分辨率开发的:
<meta name="viewport" content="target-densitydpi=device-dpi">
target-densitydpi 的可能值包括 device-dpi、high-dpi、medium-dpi、low-dpi。
如果您想针对不同的屏幕密度修改网页,请使用 -webkit-device-pixel-ratio CSS 媒体查询和/或 JavaScript 中的 window.devicePixelRatio 属性,然后将 target-densitydpi 元属性设置为 device-dpi。这样可以阻止 Android 在网页中执行缩放,并允许您通过 CSS 和 JavaScript 针对每种密度进行必要的调整。
如需详细了解如何定位设备分辨率,请参阅 Android 的 WebView 文档。
全屏浏览
还有另外两个元值,即 iOS-sfic。apple-mobile-web-app-capable 和 apple-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 媒体查询来创建此类布局。同样,无需更改标记。
移动设备优化
我们所做的大多数优化都是一开始就应该完成的工作。例如,减少网络请求数量、JS/CSS 压缩、gzip 压缩(App Engine 免费提供)以及尽量减少 DOM 操作。这些技术是常见的最佳实践,但在匆忙发布网站时,有时会被忽略。
自动隐藏地址栏
移动浏览器的屏幕空间不如桌面浏览器。 更糟糕的是,在不同的平台上,有时屏幕顶部会出现一个巨大的地址栏…即使在页面加载完成后也是如此。
一种简单的处理方法是使用 JavaScript 滚动页面。
即使只滚动一个像素,也能解决讨厌的地址栏问题。
为了在 html5rocks 上强制隐藏地址栏,我向 window 对象附加了一个 onload 事件处理脚本,并将页面垂直滚动了一个像素:
// 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 速度很慢!我们的大部分延迟来自教程页面上的第三方分享微件(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 文件即可。您最不希望看到的情况是,每次应用缓存更新时,移动浏览器都必须下载大量资源。相反,请记住,当用户访问 HTML 网页时,浏览器会隐式缓存该网页(并且该网页包含 <html manifest="..."> 属性)。