用于构建跨设备 Web 应用的无响应方法

鲍里斯·萨姆斯 (Boris Smus)
Boris Smus

媒体查询非常有用,但...

媒体查询很棒,对于希望对样式表进行小幅调整,以便为使用各种尺寸的设备的用户提供更好的体验的网站开发者而言,这是一个好消息。从本质上讲,媒体查询可让您根据屏幕尺寸自定义网站的 CSS。在深入了解本文之前,请先详细了解自适应设计,并查看一些关于媒体查询用法的精美示例:mediaqueri.es

正如 Brad Frost 在早期文章中指出的,在构建移动网站时,更改外观只是众多考虑因素之一。如果您在构建移动网站时所做的唯一工作是使用媒体查询自定义布局,那么我们遇到了以下情况:

  • 所有设备都会获取相同的 JavaScript、CSS 和资源(图片、视频),从而导致加载时间过长。
  • 所有设备都会获取相同的初始 DOM,这可能会迫使开发者编写过于复杂的 CSS。
  • 无法灵活地指定为每个设备量身定制的自定义交互。

Web 应用需要的不仅仅是媒体查询

别误解我的意思。我并不讨厌通过媒体查询使用的响应式设计 而且我绝对认为它在全世界都有一席之地此外,上述某些问题可以通过自适应图片、动态脚本加载等方法解决。但是,在某些情况下,您可能会发现自己执行的增量调整过多,最好提供不同的版本。

由于您构建的界面会增加复杂性,而您更倾向于单页 Web 应用,因此您需要执行更多操作来为每种类型的设备自定义界面。本文将向您介绍如何以最少的工作量进行这些自定义。一般方法涉及将访问者的设备分类为正确的设备类别,并为该设备提供适当的版本,同时最大限度提高不同版本之间的代码重用率。

您定位的是哪些设备类别?

市面上联网设备不计其数,而且几乎所有设备都有浏览器。复杂功能的关键在于它们的多样性:Mac 笔记本电脑、Windows 工作站、iPhone、iPad、带触控输入功能的 Android 手机、滚轮、键盘、语音输入、具有压力感应的设备、智能手表、烤面包机和冰箱等等。其中一些设备非常普遍,而另一些则非常罕见。

各种各样的设备
各种设备(来源)。

为了打造良好的用户体验,您需要了解用户是谁以及他们使用什么设备。如果您为使用鼠标和键盘的桌面设备用户构建界面,然后将界面提供给智能手机用户,界面就会显得失望,因为它是专为其他屏幕尺寸和输入模式而设计的。

此系列方法有两种极端方法:

  1. 构建一个适用于所有设备的版本。用户体验将会受到影响,因为不同的设备有不同的设计注意事项。

  2. 为您要支持的每台设备构建一个版本。这需要很长时间,因为您需要构建应用的版本过多。此外,当下一部新的智能手机推出时(大约每周发生一次),您将被迫创建另一个版本。

这方面有一个基本的权衡:您拥有的设备类别越多,可提供的用户体验越好,但您在设计、实现和维护方面所付出的工作也就越多。

出于性能方面的考虑,或者如果您要向不同设备类别提供的版本相差很大,为您决定的每个设备类别创建单独的版本不失为一个好主意。否则,自适应设计是完全合理的方法。

可能的解决方案

一种折衷方案:对设备进行分类,然后为每个类别设计可能的最佳体验。您选择的类别取决于您的产品和目标用户。下面的示例分类很好地涵盖了当今存在的各种支持网络的热门设备。

  1. 小屏幕 + 触摸(以手机为主)
  2. 大屏幕 + 触摸(大部分平板电脑)
  3. 大屏幕 + 键盘/鼠标(大部分台式机/笔记本电脑)

这只是众多可能的细分之一,但在撰写时非常有意义的一种。以上列表中缺少的是不带触摸屏的移动设备(例如功能手机、一些专用的电子书阅读器)。不过,其中大多数组件都安装有键盘导航或屏幕阅读器软件,如果您在构建网站时考虑到无障碍功能,这些方法将正常运行。

针对特定外形规格的 Web 应用示例

不少这样的例子表明,网站媒体资源针对不同外形规格提供完全不同的版本。Google 搜索和 Facebook 都是如此。这方面的注意事项包括性能(提取素材资源、呈现网页)和更普遍的用户体验。

在原生应用领域,许多开发者选择根据设备类别定制体验。例如,与 iPhone 上的 Flipboard 相比,iPad 版 Flipboard 的界面截然不同。平板电脑版本针对双手操作和水平翻转进行了优化,而手机版本则适用于单手互动和垂直翻转。许多其他 iOS 应用也提供明显不同的手机和平板电脑版本,例如 Things(待办事项列表)和 Showyou(社交视频),如下所示:

针对手机和平板电脑的重大界面自定义。
针对手机和平板电脑的重大界面自定义。

方法 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 对比 4、iPad 2 与 3 对比)。Retina 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。但是,如果您要进行客户端检测,情况会更加复杂。您有以下几种选择:

  1. 重定向到设备类型专用网址,其中包含此设备类型的版本。
  2. 动态加载设备类型专用资源。

第一种方法比较简单,需要一个重定向,例如 window.location.href = '/tablet'。不过,该位置信息现在会附加此设备类型信息,因此您可能需要使用 History API 清理网址。遗憾的是,这种方法涉及到重定向,速度可能很慢,尤其是在移动设备上。

第二种方法实现起来相对比较复杂。您需要一种机制来动态加载 CSS 和 JS,并且您可能无法执行自定义 <meta viewport> 等操作(具体取决于浏览器)。此外,由于没有重定向,因此您将停在提供的原始 HTML 上。当然,您可以使用 JavaScript 来操控它,但这可能很慢并且/或者不太简洁,具体取决于您的应用。

决定客户端还是服务器

以下是这两种方法之间的利弊:

专业客户端

  • 由于基于屏幕尺寸/功能而非 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 框架。您可以轻松将视图移到单独的文件中,为每种设备类型创建自定义视图。然后,您可以向所有设备(视图层除外)提供相同的代码。

跨设备 MVC。
跨设备 MVC。

您的项目可能具有以下结构(当然,您可以根据应用自由选择最合理的结构):

model/(共享模型)item.js item-collection.js

控制器/(共享控制器)item-controller.js

version/(设备特定内容) 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 参数支持此功能。

总结

总而言之,在构建与自适应设计不太契合的跨设备单页界面时,请执行以下操作:

  1. 选择要支持的一组设备类别,以及将设备进行分类所依据的标准。
  2. 通过强大的关注分离来构建 MVC 应用,将视图与代码库的其余部分拆分。
  3. 使用 device.js 进行客户端设备类别检测。
  4. 准备就绪后,请按设备类将脚本和样式表分别打包。
  5. 如果客户端重定向性能会带来问题,请放弃 device.js 并切换到服务器端 UA 检测。