使网站或应用程序“移动友好”的一个常见方面是包含一些调整、附加功能或界面元素,这些元素特别针对触摸屏。开发人员现在经常会问:“如何检测支持触摸的设备?”
触摸功能检测
尽管过去存在一些不兼容性和专有解决方案(例如 Mozilla 的实验性、供应商前缀事件模型),但几乎所有浏览器现在都实现了相同的 触摸事件 模型(基于 Apple 首次为 iOS Safari 引入的解决方案,该解决方案随后被其他浏览器采用,并追溯性地变成了 W3C 草案规范)。
因此,能够以编程方式检测特定浏览器是否支持触摸交互涉及一个非常简单的功能检测。
if ('ontouchstart' in window) {
/* browser with Touch Events
running on touch-capable device */
}
此代码片段在现代浏览器中可靠地工作,但旧版本臭名昭著地存在一些怪癖和不一致,需要跳过各种不同的检测策略障碍。如果您的应用程序针对这些旧版浏览器,我建议您查看 Modernizr——特别是其各种 触摸测试方法——它可以解决大多数这些问题。
我上面提到“几乎所有浏览器”都支持此触摸事件模型。这里的主要例外是 Internet Explorer。虽然 IE9 及更早版本不支持任何低级触摸交互,但 IE10 引入了对 Microsoft 自身的 指针事件 的支持。此事件模型(此后已提交给 W3C 标准化)将“指针”设备(鼠标、触笔、触摸等)统一到一个新的事件类别下。由于此模型在设计上不包含任何单独的“触摸”,因此ontouchstart
的功能检测自然不起作用。建议检测使用指针事件的浏览器是否在支持触摸的设备上运行的方法是检查navigator.maxTouchPoints
的存在及其返回值(请注意,Microsoft 的指针事件目前仍使用供应商前缀,因此在实践中我们将查找navigator.msMaxTouchPoints
)。如果该属性存在并返回的值大于0
,则表示支持触摸。
if (navigator.msMaxTouchPoints > 0) {
/* IE with pointer events running
on touch-capable device */
}
将此添加到我们之前的功能检测中——并且还包括指针事件的非供应商前缀版本以实现未来的兼容性——我们得到一个仍然相当紧凑的代码片段。
if (('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0)) {
/* browser with either Touch Events of Pointer Events
running on touch-capable device */
}
触摸检测的用途
现在,已经存在一些常用的“触摸优化”技术,这些技术利用了此类功能检测。检测触摸最常见的用例是提高触摸用户界面的响应速度。
在使用触摸屏界面时,浏览器会在触摸操作(例如点击链接或按钮)与实际点击事件触发时间之间引入人工延迟(大约 300 毫秒)。
更具体地说,在支持触摸事件的浏览器中,延迟发生在touchend
和这些浏览器为了与以鼠标为中心的脚本兼容而触发的模拟鼠标事件之间。
touchstart > [touchmove]+ > touchend > <strong>延迟</strong> > mousemove > mousedown > mouseup > click
请参阅 事件监听器测试页面 以查看事件触发的顺序,代码可在 GitHub 上获取。
引入此延迟是为了允许用户双击(例如,放大/缩小页面)而不会意外激活任何页面元素。
有趣的是,Android 上的 Firefox 和 Chrome 已为具有固定、不可缩放视口的页面去除了此延迟。
<meta name="viewport" value="... user-scalable = no ...">
请参阅 带有user-scalable=no
的事件监听器测试页面,代码可在 GitHub 上获取。
正在讨论进一步调整 Chrome 在其他情况下的行为——请参阅 Chromium 错误跟踪器中的问题 169642。
尽管此辅助功能显然是必要的,但它可能使 Web 应用程序感觉有点滞后且无响应。一个常见的技巧是检查触摸支持,如果存在,则直接对触摸事件(touchstart
——用户触摸屏幕时——或touchend
——用户抬起手指后)做出反应,而不是传统的click
。
/* if touch supported, listen to 'touchend', otherwise 'click' */
var clickEvent = ('ontouchstart' in window ? 'touchend' : 'click');
blah.addEventListener(clickEvent, function() { ... });
尽管此类优化现在被广泛使用,但它基于一个逻辑谬误,现在这种谬误正变得越来越明显。
使用指针事件的浏览器中也存在人工延迟。
pointerover > mouseover > pointerdown > mousedown > pointermove > mousemove > pointerup > mouseup > pointerout > mouseout > <strong>延迟</strong> > click
尽管可以扩展上述优化方法来检查navigator.maxTouchPoints
,然后将我们的监听器连接到pointerup
而不是click
,但有一种更简单的方法:将元素的 touch-action
CSS 属性 设置为none
可以消除延迟。
/* suppress default touch action like double-tap zoom */
a, button {
-ms-touch-action: none;
touch-action: none;
}
错误的假设
需要注意的是,这些基于触摸可用性的优化类型存在一个根本缺陷:它们根据设备功能对用户行为做出假设。更明确地说,上面的示例假设,因为设备能够进行触摸输入,所以用户实际上会将触摸作为与之交互的唯一方式。
几年前,这个假设可能有一定的道理,当时唯一具有触摸输入功能的设备是经典的“移动设备”和“平板电脑”。在这里,触摸屏是唯一可用的输入方法。然而,近几个月来,我们看到了一类全新的设备,它们既具有传统的笔记本电脑/台式电脑外形(包括鼠标、触控板、键盘),又具有触摸屏,例如各种 Windows 8 机器 或 Google 的 Chromebook Pixel。
顺便说一句,即使在手机或平板电脑的情况下,在某些平台上用户也可以添加其他输入设备。虽然 iOS 仅支持为 iPhone/iPad 配对额外的蓝牙键盘以进行纯文本输入,但 Android 和 Blackberry OS 也允许用户添加鼠标。
在 Android 上,此鼠标将完全像“触摸”一样工作,甚至触发相同的触摸事件和模拟鼠标事件序列,包括两者之间令人讨厌的延迟——因此像我们上面示例中的优化仍然可以正常工作。然而,Blackberry OS 仅触发鼠标事件,从而导致下面概述的相同问题。
此更改的影响正在慢慢地为开发人员所认识:触摸支持不再一定意味着“移动设备”,更重要的是,即使触摸可用,它也可能不是用户选择的首要或唯一输入方法。事实上,用户甚至可能在交互过程中切换任何可用的输入方法。
上面这些简单的代码片段在这一类新设备上可能会产生相当恼人的后果。在使用触摸事件的浏览器中
var clickEvent = ('ontouchstart' in window ? 'touchend' : 'click');
基本上是在说“如果设备支持触摸,则只监听touchend
而不监听click
”——这在多输入设备上会立即阻止通过鼠标、触控板或键盘进行任何交互。
触摸或鼠标?
那么,对于可能也具有其他输入方法的支持触摸的设备,如何解决这个新的难题呢?虽然一些开发人员已开始考虑用额外的用户代理嗅探来补充触摸功能检测,但我认为答案——就像 Web 开发中的许多其他情况一样——是接受我们无法完全检测或控制用户如何与我们的网站和应用程序交互,并且要与输入无关。不要做出假设,我们的代码应该适应所有情况。具体来说,不要将是否对click
或touchend
/touchstart
做出反应的决策视为互斥的,而应将它们都视为互补的。
当然,这可能涉及更多代码,但最终结果是我们的应用程序将适用于最多的用户。一种方法(对于那些努力使他们以鼠标为中心的界面也适用于键盘用户的开发人员来说已经很熟悉了)是简单地“加倍”您的事件监听器(同时注意通过停止触摸事件后触发的模拟鼠标事件来防止功能触发两次)。
blah.addEventListener('touchend', function(e) {
/* prevent delay and simulated mouse events */
e.preventDefault();
someFunction()
});
blah.addEventListener('click', someFunction);
如果这对于您来说还不够“DRY”,当然还有更花哨的方法,例如只为click
定义您的函数,然后通过显式触发该处理程序来绕过令人讨厌的延迟。
blah.addEventListener('touchend', function(e) {
/* prevent delay and simulated mouse events */
e.preventDefault();
/* trigger the actual behavior we bound to the 'click' event */
e.target.click();
})
blah.addEventListener('click', function() {
/* actual functionality */
});
不过,最后一个代码片段并没有涵盖所有可能的情况。有关相同原理的更强大的实现,请参阅来自 FT 实验室 的 FastClick 脚本。
与输入无关
当然,与触摸设备上的延迟作斗争并不是开发人员想要检查触摸功能的唯一原因。当前的讨论——例如 Modernizr 中关于 检测鼠标用户 的此问题——现在围绕着为触摸用户提供与鼠标或键盘完全不同的界面,以及特定浏览器/设备是否支持悬停等功能。甚至在 JavaScript 之外,类似的概念(pointer
和hover
媒体功能)也已针对 媒体查询级别 4 提出。但原理仍然相同:由于现在有常见的多种输入设备,因此不再容易(在许多情况下,不可能)确定用户是否在仅支持触摸的设备上。
Microsoft 指针事件规范中采用的更通用的方法——该方法已安排在 Chrome 等其他浏览器中实施——是朝着正确方向迈出的一步(尽管它仍然需要额外处理键盘用户)。同时,开发人员应注意不要从触摸支持检测中得出错误的结论,并避免无意中锁住越来越多的潜在多输入用户。
更多链接
- 媒体查询级别 4 的优缺点
- 在所有浏览器中处理多点触控和鼠标输入
- Hand.js:一个用于在每个浏览器上支持指针事件的 polyfill
- 触摸和鼠标——再次携手
- 支持 MS 指针事件的原型 Chromium 版本
- 可触摸的网页(德语)
- 跨设备 Web 上的通用输入
关于 Patrick H. Lauke
@patrick_h_lauke 自 2001 年初以来一直参与标准和可访问性方面的讨论。直到最近,Patrick 还在 Opera Software 的开发者关系团队担任 Web 布道师。作为一位直言不讳的可访问性和标准倡导者,Patrick 倾向于采用务实的实践方法,而不是纯粹的理论性、高级讨论。
关于 Robert Nyman [荣誉编辑]
技术布道师和 Mozilla Hacks 编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客文章。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直在从事 Web 前端开发工作——在瑞典和纽约市。他还定期在 http://robertnyman.com 上发表博客文章,并且喜欢旅行和结识新朋友。
21 条评论