媒体查询很棒,但…
媒体查询非常棒,对于想要对其样式表进行小幅调整以在各种尺寸的设备上为用户提供更好体验的网站开发者来说,简直是天赐之物。媒体查询实际上可让您根据屏幕尺寸自定义网站的 CSS。在深入阅读本文之前,请先详细了解自适应设计,并查看以下网址中有关媒体查询用法的精美示例:mediaqueri.es。
正如 Brad Frost 在之前的文章中指出的那样,在为移动 Web 构建内容时,更改外观只是需要考虑的众多因素之一。如果您在构建移动网站时唯一要做的就是使用媒体查询自定义布局,那么就会出现以下情况:
- 所有设备都会获得相同的 JavaScript、CSS 和素材资源(图片、视频),导致加载时间比必要的时间长。
- 所有设备都会获得相同的初始 DOM,这可能会迫使开发者编写过于复杂的 CSS。
- 无法灵活地指定针对每种设备量身定制的自定义互动。
Web 应用需要的不只是媒体查询
请不要误解我的意思。我并不讨厌通过媒体查询实现自适应设计,并且绝对认为它在世界上有一席之地。此外,上述部分问题可以通过自适应图片、动态脚本加载等方法来解决。不过,在某个时间点,您可能会发现自己进行了过多的增量调整,最好是提供不同的版本。
随着您构建的界面越来越复杂,并且您越来越倾向于使用单页 Web 应用,您会希望采取更多措施来针对每种类型的设备自定义界面。本文将介绍如何以最少的精力完成这些自定义设置。一般方法是将访问者的设备分类到正确的设备类别中,并向该设备提供适当的版本,同时最大限度地提高版本之间的代码重用率。
您面向的是哪些设备类别?
目前市面上有很多联网设备,几乎所有设备都有浏览器。复杂之处在于它们的多样性:Mac 笔记本电脑、Windows 工作站、iPhone、iPad、支持触控输入、滚轮、键盘、语音输入的 Android 手机、支持压力感应的设备、智能手表、烤面包机和冰箱等等。其中一些设备随处可见,而另一些则非常罕见。
为了打造良好的用户体验,您需要了解用户是谁以及他们使用的是什么设备。如果您为使用鼠标和键盘的桌面用户构建界面,然后将其提供给智能手机用户,那么您的界面会令用户感到沮丧,因为它针对的是其他屏幕尺寸和其他输入方式。
方法谱系的两端是两种极端方法:
构建一个可在所有设备上运行的版本。由于不同设备的设计考虑因素不同,因此用户体验会受到影响。
为要支持的每种设备构建一个版本。这会花费很长时间,因为您需要构建过多的应用版本。此外,当下一款新智能手机推出时(大约每周都会推出),您将不得不创建另一个版本。
这里存在一个根本性的权衡:设备类别越多,您能提供的用户体验就越好,但设计、实现和维护所需的工作量也就越大。
出于性能方面的考虑,或者如果您想向不同设备类别投放的版本差异很大,那么为每个设备类别单独创建一个版本可能是不错的选择。否则,自适应网页设计也是一种完全合理的方法。
可能的解决方案
以下是一种折中方案:将设备分为若干类别,并为每个类别设计尽可能最佳的体验。您选择的类别取决于您的商品和目标用户。以下是一个示例分类,可很好地涵盖目前流行的支持网络的设备。
- 小屏幕 + 触控(主要是手机)
- 大屏设备 + 触控(主要是平板电脑)
- 大屏设备 + 键盘/鼠标(主要是桌面设备/笔记本电脑)
这只是众多可能细分方式中的一种,但在撰写本文时,这种细分方式非常合理。上述列表中未列出没有触摸屏的移动设备(例如功能手机、部分专用电子书阅读器)。不过,其中大多数都安装了键盘导航或屏幕阅读器软件,如果您在构建网站时考虑到无障碍功能,这些软件就能正常运行。
特定规格的 Web 应用示例
有许多网站媒体资源会针对不同的设备规格提供完全不同的版本。Google 搜索和 Facebook 都会这样做。这方面的考虑因素包括性能(提取素材资源、呈现网页)和更一般的用户体验。
在原生应用领域,许多开发者选择针对特定设备类别量身打造应用体验。例如,iPad 版 Flipboard 的界面与 iPhone 版 Flipboard 的界面截然不同。平板电脑版本针对双手操作和横向翻转进行了优化,而手机版本则旨在实现单手互动和纵向翻转。许多其他 iOS 应用也提供了截然不同的手机和平板电脑版本,例如待办事项和秀友(社交视频),如下所示:
方法 1:服务器端检测
在服务器上,我们对所处理的设备的了解要少得多。最实用的线索可能是用户代理字符串,该字符串通过每个请求中的 User-Agent 标头提供。因此,相同的 UA 嗅探方法也适用于此处。事实上,DeviceAtlas 和 WURFL 项目已经实现了这一点(并提供了有关设备的许多其他信息)。
遗憾的是,每种方法都有自己的挑战。WURFL 非常大,包含 20MB 的 XML,可能会导致每个请求产生大量的服务器端开销。有些项目出于性能方面的考虑,会拆分 XML。DeviceAtlas 不是开源的,需要付费许可才能使用。
此外,还有一些更简单、免费的替代方案,例如 Detect Mobile Browsers 项目。当然,缺点是设备检测的全面性必然会降低。此外,它仅区分移动设备和非移动设备,仅通过一组临时调整提供有限的平板电脑支持。
方法 2:客户端检测
我们可以通过功能检测来详细了解用户的浏览器和设备。我们需要确定的主要事项是设备是否具有触控功能,以及屏幕是大还是小。
我们需要划定一个界限,以区分小型和大型触控设备。那么像 5 英寸 Galaxy Note 这样的边缘情况呢?下图显示了许多热门 Android 和 iOS 设备叠加在一起(具有相应的屏幕分辨率)。星号表示相应设备采用或可以采用双倍密度。虽然像素密度可能会翻倍,但 CSS 仍会报告相同的大小。
关于 CSS 中的像素,这里简要说明一下:移动网站上的 CSS 像素与屏幕像素并不相同。iOS Retina 设备引入了将像素密度加倍的做法(例如 iPhone 3GS 与 iPhone 4、iPad 2 与 iPad 3)。Retina Mobile Safari UA 仍会报告相同的 device-width,以避免破坏网站。与其他设备(例如Android)获得更高分辨率的显示屏,它们也采用了相同的设备宽度技巧。
不过,这项决策的复杂之处在于,您需要同时考虑竖屏模式和横屏模式。我们不希望每次重新调整设备方向时都重新加载网页或加载其他脚本,但我们可能希望以不同的方式呈现网页。
在下图中,正方形表示每个设备的最大尺寸,这是通过叠加竖屏和横屏轮廓(并完成正方形)得出的:
通过将阈值设置为 650px,我们将 iPhone、Galaxy Nexus 分类为“小触控设备”,并将 iPad、Galaxy Tab 分类为“平板电脑”。在这种情况下,中性 Galaxy Note 会被归类为“手机”,并获得手机布局。
因此,合理的策略可能如下所示:
if (hasTouch) {
if (isSmall) {
device = PHONE;
} else {
device = TABLET;
}
} else {
device = DESKTOP;
}
查看功能检测方法的实际应用示例。
另一种方法是使用 UA 嗅探来检测设备类型。基本上,您需要创建一组启发式方法,并将其与用户的 navigator.userAgent 进行匹配。伪代码如下所示:
var ua = navigator.userAgent;
for (var re in RULES) {
if (ua.match(re)) {
device = RULES[re];
return;
}
}
查看用户代理检测方法的实际应用。
关于客户端加载的说明
如果您在服务器上进行 UA 检测,则可以在收到新请求时决定要提供哪些 CSS、JavaScript 和 DOM。不过,如果您要进行客户端检测,情况会更复杂。您有以下几种选择:
- 重定向到包含相应设备类型版本的特定于设备类型的网址。
- 动态加载特定于设备类型的资源。
第一种方法非常简单,只需要重定向,例如 window.location.href = '/tablet'。不过,位置信息现在会附加此设备类型信息,因此您可能需要使用 History API 来清理网址。遗憾的是,此方法涉及重定向,这可能会很慢,尤其是在移动设备上。
第二种方法实现起来要复杂得多。您需要一种动态加载 CSS 和 JS 的机制,并且(取决于浏览器)可能无法执行自定义 <meta viewport> 等操作。此外,由于没有重定向,您只能使用所提供的原始 HTML。当然,您可以使用 JavaScript 对其进行操作,但根据您的应用,这可能会很慢和/或不够优雅。
确定是客户端还是服务器
以下是这些方法之间的利弊对比:
专业版客户端:
- 更具前瞻性,因为它是基于屏幕尺寸/功能而非 UA。
- 无需不断更新 UA 列表。
专业版服务器:
- 完全控制向哪些设备提供哪个版本。
- 性能更佳:无需客户端重定向或动态加载。
我个人偏好从 device.js 和客户端检测开始。随着应用的发展,如果您发现客户端重定向会严重影响性能,可以轻松移除 device.js 脚本,并在服务器上实现 UA 检测。
device.js 简介
Device.js 是基于媒体查询进行语义设备检测的起点,无需特殊的服务器端配置,从而节省了用户代理字符串解析所需的时间和精力。
其思路是,您在 <head> 的顶部提供搜索引擎友好的标记 (link rel=alternate),指明您要提供的网站版本。
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
接下来,您可以自行进行服务器端 UA 检测并处理版本重定向,也可以使用 device.js 脚本进行基于功能的客户端重定向。
如需了解详情,请参阅 device.js 项目页面,以及使用 device.js 进行客户端重定向的虚假应用。
建议:采用具有特定于设备规格的视图的 MVC
到目前为止,您可能认为我是在建议您构建三个完全不同的应用,每种设备类型对应一个应用。否!代码共享是关键。
希望您一直在使用类似 MVC 的框架,例如 Backbone、Ember 等。如果您一直在使用,那么您应该熟悉关注点分离原则,特别是您的界面(视图层)应与逻辑(模型层)分离。如果您是新手,可以先从这些 MVC 资源和 JavaScript 中的 MVC 开始学习。
跨设备功能可完美融入您现有的 MVC 框架。您可以轻松地将视图移到单独的文件中,为每种设备类型创建自定义视图。然后,您可以向所有设备提供相同的代码,但视图层除外。
您的项目可能具有以下结构(当然,您可以根据自己的应用自由选择最合理的结构):
models/(共享模型) item.js item-collection.js
controllers/(共享控制器) item-controller.js
versions/(设备专用内容) tablet/ desktop/ phone/(手机专用代码) style.css index.html views/ item.js item-list.js
这种结构可让您完全控制每个版本加载的资源,因为您为每种设备都提供了自定义 HTML、CSS 和 JavaScript。这种方式非常强大,可以实现最精简、性能最高的跨设备 Web 开发方式,而无需依赖自适应图片等技巧。
运行您喜爱的 build 工具后,您将把所有 JavaScript 和 CSS 连接并缩小为单个文件,以加快加载速度,并且您的生产 HTML 看起来会像以下内容(对于手机,使用 device.js):
<!doctype html>
<head>
<title>Mobile Web Rocks! (Phone Edition)</title>
<!-- Every version of your webapp should include a list of all
versions. -->
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
<link rel="alternate" href="http://m.foo.com" id="phone"
media="only screen and (max-device-width: 650px)">
<link rel="alternate" href="http://tablet.foo.com" id="tablet"
media="only screen and (min-device-width: 650px)">
<!-- Viewport is very important, since it affects results of media
query matching. -->
<meta name="viewport" content="width=device-width">
<!-- Include device.js in each version for redirection. -->
<script src="device.js"></script>
<link rel="style" href="phone.min.css">
</head>
<body>
<script src="phone.min.js"></script>
</body>
请注意,(touch-enabled: 0) 媒体查询是非标准查询(仅在 Firefox 中通过 moz 供应商前缀实现),但 device.js 可以正确处理该查询(得益于 Modernizr.touch)。
版本替换
设备检测有时可能会出错,在某些情况下,用户可能更喜欢在手机上查看平板电脑布局(也许他们使用的是 Galaxy Note),因此,如果用户想要手动替换,请务必让他们选择使用哪个版本的网站。
通常的做法是在移动版网站中提供指向桌面版网站的链接。实现此功能非常简单,但 device.js 通过 device GET 参数支持此功能。
总结
总而言之,在构建无法完全适应自适应设计世界的跨设备单页界面时,请执行以下操作:
- 选择要支持的一组设备类,以及将设备分类为不同类的标准。
- 构建 MVC 应用时,要将视图与代码库的其余部分严格分离。
- 使用 device.js 在客户端检测设备类别。
- 准备就绪后,将脚本和样式表打包到每个设备类别的其中一个中。
- 如果客户端重定向性能存在问题,请放弃 device.js 并改用服务器端 UA 检测。