媒体查询非常棒,但…
媒体查询非常强大,对于想要对样式表进行微调以便在各种尺寸的设备上为用户提供更好体验的网站开发者来说,简直是天赐良机。媒体查询本质上可让您根据屏幕尺寸自定义网站的 CSS。在深入阅读本文之前,请先详细了解自适应设计,并访问 mediaqueri.es,查看一些媒体查询使用示例。
正如 Brad Frost 在一篇早期文章中指出的那样,在为移动 Web 构建应用时,需要考虑的因素有很多,而更改外观只是其中之一。如果您在构建移动网站时只使用媒体查询自定义布局,那么就会出现以下情况:
- 所有设备都会获取相同的 JavaScript、CSS 和素材资源(图片、视频),导致加载时间过长。
- 所有设备都会获得相同的初始 DOM,这可能会迫使开发者编写过于复杂的 CSS。
- 很难灵活地指定适合每部设备的自定义互动。
除了媒体查询之外,Web 应用还需要更多功能
请不要误会。我并不反对通过媒体查询实现响应式设计,而且我认为这种设计在业界一定有用武之地。此外,上述一些问题可以通过响应式图片、动态脚本加载等方法来解决。不过,在某个时候,您可能会发现自己进行了太多的增量调整,因此最好提供不同的版本。
随着您构建的界面越来越复杂,并且您开始倾向于使用单页 Web 应用,您需要采取更多措施来针对每种类型的设备自定义界面。本文将介绍如何以最少的努力完成这些自定义。常规方法包括将访问者的设备划分到正确的设备类别,并向该设备提供适当的版本,同时最大限度地实现版本之间的代码复用。
您要定位到哪些设备类别?
市面上有大量联网设备,几乎所有这些设备都配备了浏览器。复杂性在于它们的多样性:Mac 笔记本电脑、Windows 工作站、iPhone、iPad、支持触控输入的 Android 手机、滚轮、键盘、语音输入、具有压力感知功能的设备、智能手表、烤面包机和冰箱等等。其中一些设备非常常见,而另一些设备则非常罕见。
为了打造良好的用户体验,您需要了解用户是谁以及他们使用的是哪些设备。如果您为使用鼠标和键盘的桌面用户构建界面,并将其交给智能手机用户,那么您的界面会让用户感到沮丧,因为它是为其他屏幕尺寸和其他输入模式而设计的。
这两种方法的极端分别是:
构建一个适用于所有设备的版本。由于不同设备有不同的设计注意事项,因此用户体验会受到影响。
为您要支持的每台设备构建一个版本。这将需要很长时间,因为您将构建太多版本的应用。此外,当下一部新款智能手机发布时(大约每周一次),您将不得不再创建一个版本。
这里有一个基本权衡:设备类别越多,您可以提供的用户体验就越好,但设计、实现和维护的工作量也会越大。
出于性能方面的原因,或者您要向不同设备类别分发的版本差异很大时,为您决定的每个设备类别创建单独的版本可能是一个好主意。否则,自适应网站设计是一个非常合理的方法。
可能的解决方案
我们可以采取折衷方案:将设备分类,并为每个类别设计尽可能出色的体验。您选择的类别取决于您的产品和目标用户。下面是一个示例分类,涵盖了当今流行的支持 Web 的设备。
- 小屏幕 + 触控(主要是手机)
- 大屏设备 + 触摸屏(主要是平板电脑)
- 大屏设备 + 键盘/鼠标(主要是桌面设备/笔记本电脑)
这只是许多可能的分解方式之一,但在撰写本文时,这种分解方式非常有用。上述列表中未涵盖无触摸屏的移动设备(例如功能手机、某些专用电子书阅读器)。不过,其中大多数设备都安装了键盘导航或屏幕阅读器软件,如果您在构建网站时考虑了无障碍功能,这些软件就能正常运行。
特定于外形规格的 Web 应用示例
有很多网站资源针对不同外形规格的设备提供完全不同的版本。Google 搜索和 Facebook 都会这样做。这方面的注意事项包括性能(提取资源、呈现网页)和更常规的用户体验。
在原生应用领域,许多开发者会选择根据设备类别量身定制体验。例如,iPad 版 Flipboard 的界面与 iPhone 上的 Flipboard 截然不同。平板电脑版本针对双手使用和横向翻转进行了优化,而手机版本则针对单手互动和纵向翻转进行了优化。许多其他 iOS 应用也提供明显不同的手机版和平板电脑版,例如Things(待办事项)和 Showyou(社交视频),如下所示:
方法 1:服务器端检测
在服务器上,我们对所处理的设备的了解要有限得多。最有用的线索可能是用户代理字符串,它会在每次请求中通过 User-Agent 标头提供。因此,这里同样适用 UA 嗅探方法。事实上,DeviceAtlas 和 WURFL 项目已经做到了这一点(并提供了大量有关设备的其他信息)。
遗憾的是,每种方法都有各自的挑战。WURFL 非常大,包含 20MB 的 XML,可能会导致每个请求产生大量服务器端开销。有些项目出于性能方面的原因会拆分 XML。DeviceAtlas 不是开源的,需要付费许可才能使用。
还有更简单、免费的替代方案,例如检测移动浏览器项目。当然,缺点是设备检测功能势必会不那么全面。此外,它只会区分移动设备和非移动设备,仅通过一组临时调整提供有限的平板电脑支持。
方法 2:客户端检测
通过使用功能检测,我们可以详细了解用户的浏览器和设备。我们需要确定的主要事项是设备是否支持触控,以及设备是小屏幕还是大屏幕。
我们需要在某个位置划定界线,以区分小屏幕和大屏幕触摸设备。5 英寸的 Galaxy Note 等极端情况如何?下图显示了一系列热门 Android 和 iOS 设备的叠加图(以及相应的屏幕分辨率)。星号表示设备采用或可以采用双倍密度。虽然像素密度可能会翻倍,但 CSS 仍会报告相同的尺寸。
关于 CSS 中的像素,我们来简单介绍一下:移动网站上的 CSS 像素与屏幕像素不同。iOS 视网膜设备引入了将像素密度翻倍的做法(例如 iPhone 3GS 与 4、iPad 2 与 3)。为了避免网页出现问题,视网膜版 Mobile Safari UA 仍会报告相同的设备宽度。如其他设备(例如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 检测方法的实际运作示例。
关于客户端加载的说明
如果您在服务器上进行 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,请先参阅这些有关 MVC 的资源和 JavaScript 中的 MVC。
跨设备解决方案可轻松融入现有的 MVC 框架。您可以轻松将视图移至单独的文件中,为每种类型的设备创建自定义视图。然后,您可以向所有设备(视图层除外)提供相同的代码。
您的项目可能具有以下结构(当然,您可以根据自己的应用自由选择最合适的结构):
models/(共享模型) item.js item-collection.js
controllers/ (shared controllers) item-controller.js
versions/(设备专用内容) tablet/ desktop/ phone/(手机专用代码) style.css index.html views/ item.js item-list.js
这种结构可让您完全控制每个版本加载的资源,因为您为每部设备提供了自定义 HTML、CSS 和 JavaScript。这非常强大,可以让您以最精简、性能最高的方式为跨设备 Web 开发应用,而无需依赖自适应图片等技巧。
运行您喜爱的构建工具后,您需要将所有 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 检测。