第一次重聚
简介
近 30 年来,台式计算体验一直以键盘和鼠标或触控板作为主要用户输入设备。不过,在过去十年间,智能手机和平板电脑带来了一种新的交互范式:触控。随着支持触控的 Windows 8 设备的推出,以及现在出色的支持触控的 Chromebook Pixel 的发布,触控功能已成为桌面体验的预期部分。其中最大的挑战之一是,打造的体验不仅适用于触控设备和鼠标设备,还适用于用户会同时使用这两种输入法(有时是同时!)的设备。
本文将帮助您了解触控功能是如何内置到浏览器中的、如何将这种新界面机制集成到现有应用中,以及如何将触控功能与鼠标输入搭配使用。
Web 平台中的触控状态
iPhone 是第一个在网络浏览器中内置有专用触摸 API 的热门平台。其他一些浏览器供应商也创建了类似的 API 接口,这些接口旨在与 iOS 实现兼容,目前由“触摸事件版本 1”规范进行说明。桌面设备上的 Chrome 和 Firefox、iOS 和 Chrome 中的 Safari、Android 设备上的 Android 浏览器,以及 Blackberry 浏览器等其他移动浏览器均支持触摸事件。
我的同事 Boris Smus 撰写了一篇出色的 HTML5Rocks 教程,介绍了触摸事件。如果您之前从未接触过触摸事件,不妨先从这篇教程入手。事实上,如果您之前没有使用过触摸事件,请先阅读这篇文章,然后再继续操作。请继续,我会等您。
已全部完成?现在,您已经掌握了触摸事件的基础知识。编写支持触摸的互动时,面临的挑战在于,触摸互动可能与鼠标(以及模拟鼠标的触控板和轨迹球)事件大不相同。虽然触摸界面通常会尝试模拟鼠标,但这种模拟并不完美或完整;您确实需要处理这两种互动方式,并且可能需要单独支持每种界面。
最重要的是:用户可能同时使用触控板和鼠标
许多开发者构建的网站会静态检测环境是否支持触摸事件,然后假定它们只需支持触摸(而非鼠标)事件。现在,这种假设是错误的。事实上,触摸事件的存在并不意味着用户主要使用的是触摸输入设备。Chromebook Pixel 和部分 Windows 8 笔记本电脑等设备目前同时支持鼠标和触控输入法,并且很快会支持更多输入法。在这些设备上,用户很自然地会同时使用鼠标和触摸屏与应用进行交互,因此“支持触摸”不同于“不需要鼠标支持”。您不能将问题视为“我必须编写两种不同的互动方式并在它们之间切换”,而需要仔细考虑这两种互动方式将如何协同工作以及独立工作。在 Chromebook Pixel 上,我经常使用触控板,但也会伸手触摸屏幕。在同一应用或网页中,我会根据当时最自然的方式来操作。另一方面,某些触摸屏笔记本电脑用户很少使用触摸屏,因此触摸输入不应停用或阻碍鼠标控制。
遗憾的是,很难确定用户的浏览器环境是否支持触摸输入;理想情况下,桌面设备上的浏览器应始终指明支持触摸事件,以便随时连接触摸屏显示屏(例如,通过 KVM 连接的触摸屏可用时)。出于所有这些原因,您的应用不应尝试在触摸和鼠标之间切换,而应同时支持这两种输入方式!
同时支持鼠标和触控
第 1 点 - 点击和点按 - 事物的“自然”顺序
第一个问题是,触摸界面通常会尝试模拟鼠标点击,这是显而易见的,因为触摸界面需要在之前仅与鼠标事件互动的应用中运行!您可以将其用作快捷方式,因为无论用户是使用鼠标点击还是用手指点按屏幕,“点击”事件都会继续触发。不过,此快捷方式存在几个问题。
首先,在设计更高级的触摸互动时,您必须小心谨慎:当用户使用鼠标时,系统会通过点击事件进行响应,但当用户触摸屏幕时,系统会同时触发触摸事件和点击事件。对于单次点击,事件的顺序如下:
- touchstart
- touchmove
- Touchend
- 鼠标悬停
- mousemove
- mousedown
- mouseup
- 点击
当然,这意味着如果您正在处理像 touchstart 这样的触摸事件,则需要确保不会同时处理相应的 mousedown 和/或点击事件。如果您可以取消触摸事件(在事件处理程序内调用 preventDefault()),则不会为触摸生成任何鼠标事件。触摸处理脚本最重要的规则之一是:
不过,这也会阻止其他默认浏览器行为(例如滚动)。不过,通常您会在处理程序中完全处理轻触事件,并且您希望停用默认操作。一般来说,您需要处理并取消所有轻触事件,或者避免为该事件设置处理脚本。
其次,当用户在移动设备上点按网页中的元素时,未针对移动设备交互进行设计的网页在触摸开始事件和处理鼠标事件 (mousedown) 之间至少会延迟 300 毫秒。您可以使用 Chrome 来实现此目的,只需在 Chrome 开发者工具中开启“模拟触摸事件”,即可在非触摸系统上测试触摸界面!
此延迟是为了让浏览器有时间确定用户是否在执行其他手势,特别是点按两次缩放。显然,如果您希望对手指触摸做出即时响应,这可能会出现问题。我们正在进行相关工作,以尝试限制这种延迟自动发生的情况。
避免此延迟的第一种也是最简单的方法是“告知”移动浏览器您的网页不需要缩放,这可以使用固定视口来实现,例如在网页中插入以下代码:
<meta name="viewport" content="width=device-width,user-scalable=no">
当然,这并不总是适用的,因为这会停用双指张合缩放功能,而此功能可能出于无障碍功能方面的考虑而必不可少,因此请尽量少用此方法(如果您确实停用了用户缩放功能,则可能需要提供其他方法来提高应用中的文本可读性)。此外,对于支持触控的桌面设备上的 Chrome,以及移动平台上的其他浏览器,如果网页的视口不可伸缩,则不适用此延迟。
#2:触摸不会触发鼠标移动事件
在此需要特别注意的是,在触摸界面中模拟鼠标事件通常不会扩展到模拟 mousemove 事件。因此,如果您构建了一个使用 mousemove 事件的漂亮鼠标驱动型控件,除非您还专门添加了 touchmove 处理脚本,否则它可能无法在触摸设备上正常运行。
浏览器通常会自动针对 HTML 控件上的触摸互动实现适当的互动。例如,HTML5 Range 控件只会在您使用触摸互动时起作用。不过,如果您实现了自己的控件,它们可能无法支持点击和拖动类型的互动;事实上,一些常用的库(例如 jQueryUI)尚不支持以这种方式进行原生触摸互动(不过,对于 jQueryUI,有几种猴子补丁可以修复此问题)。在将 Web Audio Playground 应用升级为支持触控功能时,我遇到的第一个问题就是这个问题 - 滑块基于 jQueryUI,因此无法通过点击和拖动进行互动。我改用 HTML5 Range 控件,问题解决了。当然,我也可以直接添加 touchmove 处理程序来更新滑块,但有一个问题...
#3:Touchmove 和 MouseMove 并不相同
我发现一些开发者会陷入一个误区,即让 touchmove 和 mousemove 处理脚本调用相同的代码路径。这些事件的行为非常接近,但存在细微差异,尤其是触摸事件始终定位到触摸开始的元素,而鼠标事件定位到当前位于鼠标光标下的元素。 因此,我们有 mouseover 和 mouseout 事件,但没有对应的 touchover 和 touchout 事件,只有 touchend 事件。
最常见的后果是,您不小心移除了(或重新定位了)用户开始触摸的元素。例如,假设某个图片轮播界面在整个轮播界面中都带有触摸处理程序,以支持自定义滚动行为。随着可用图片的变化,您可以移除一些 <img>
元素并添加其他元素。如果用户恰巧开始轻触其中一个图片,而您随后移除了该图片,您的处理脚本(位于 img 元素的祖先上)只会停止接收轻触事件(因为它们会被分派给不再位于树中的目标)- 这看起来就像用户将手指按住在一个位置,即使他们可能已经移动并最终移除了手指。
当然,您可以通过避免在触摸处于活跃状态时移除具有(或具有具有)触摸处理脚本的元素来避免此问题。或者,最佳做法是,不要注册静态 touchend/touchmove 处理脚本,而是等到收到 touchstart 事件,然后将 touchmove/touchend/touchcancel 处理脚本添加到 touchstart 事件的目标(并在结束/取消时将其移除)。这样,即使目标元素被移动/移除,您也将继续接收触摸事件。您可以点击此处稍微试玩一下 - 触摸红色方框,然后按住 Escape 键将其从 DOM 中移除。
#4:触摸和悬停
鼠标指针隐喻将光标位置与主动选择分离开来,这让开发者可以使用悬停状态来隐藏和显示可能与用户相关的信息。不过,目前大多数触控界面都无法检测手指在目标上“悬停”的情况,因此,请勿根据悬停提供具有重要语义的信息(例如,提供“这是什么控件?”弹出式窗口),除非您还提供一种触控友好的访问此类信息的方式。您需要谨慎使用悬停功能向用户传递信息。
不过,有趣的是,在某些情况下,CSS :hover 伪类可以由触摸界面触发 - 点按元素会使其在手指按下时处于 :active 状态,同时也获得 :hover 状态。(在 Internet Explorer 中,:hover 仅在用户按下手指时有效 - 其他浏览器会让 :hover 保持有效状态,直到下次点按或鼠标移动。)这是一种使弹出菜单在触摸界面上正常运行的好方法,使元素处于活动状态的副作用是还会应用 :hover 状态。例如:
<style>
img ~ .content {
display:none;
}
img:hover ~ .content {
display:block;
}
</style>
<img src="/awesome.png">
<div class="content">This is an awesome picture of me</div>
用户点按另一个元素后,该元素将不再有效,悬停状态也会消失,就像用户使用鼠标指针将其从元素上移开一样。您可能需要将内容封装在 <a>
元素中,以便将其也设置为 Tab 键停止点。这样,用户就可以通过鼠标悬停或点击、轻触点按或按键操作来切换额外信息,而无需使用 JavaScript。当我开始着手让 Web Audio Playground 与触摸界面良好配合时,发现我的弹出式菜单已经可以很好地支持触摸操作,这让我感到非常惊喜,因为我使用的就是这种结构!
上述方法非常适用于基于鼠标指针的界面,也适用于触控界面。这与在悬停时使用“title”属性形成对比,后者在元素处于启用状态时不会显示:
<img src="/awesome.png" title="this doesn't show up in touch">
#5:触控与鼠标的精确度
虽然鼠标在概念上与现实存在脱离,但事实证明鼠标是非常准确的,因为底层操作系统通常会跟踪光标的精确像素精度。另一方面,移动开发者已经了解到,手指在触摸屏上的触摸并不那么准确,这主要是因为手指与屏幕接触时所占的表面积较大(部分原因是手指遮挡了屏幕)。
许多个人和公司都进行了大量用户研究,探讨如何设计适合手指互动的应用和网站,并且还撰写了许多关于此主题的书籍。基本建议是通过增加内边距来增大触摸目标的大小,并通过增加元素之间的边距来降低错误点按的可能性。(边距不包含在触摸和点击事件的命中检测处理中,而内边距包含在内。)我要对网络音频园地进行的主要修复之一是增加连接点的大小,以便更轻松地触摸它们。
许多处理基于触摸的界面的浏览器供应商还在浏览器中引入了逻辑,以便在用户轻触屏幕时定位到正确的元素,并降低错误点击的可能性。不过,这通常只会更正点击事件,而不会更正移动事件(尽管 Internet Explorer 似乎也会修改 mousedown/mousemove/mouseup 事件)。
#6:将触摸处理程序控制在合理范围内,否则会导致滚动卡顿
还有一点也很重要:让触摸处理程序仅包含您需要的元素;触摸元素的带宽可能非常高,因此,务必要避免在滚动元素上使用触摸处理程序(因为您的处理可能会干扰浏览器优化以实现快速无卡顿的触摸滚动 - 现代浏览器会尝试在 GPU 线程上滚动,但如果它们必须先通过 JavaScript 检查应用是否要处理每个触摸事件,就不可能做到这一点)。您可以查看此行为的示例。
为了避免此问题,我们建议您遵循以下准则:如果您只在界面的一小部分处理触摸事件,请仅在该部分附加触摸处理脚本(例如,不要在网页的 <body>
上附加触摸处理脚本);简而言之,请尽可能限制触摸处理脚本的范围。
#7:多点触控
最后一个有趣的挑战是,虽然我们一直将其称为“触摸”界面,但几乎所有情况下,它实际上都支持多点触控,也就是说,API 一次会提供多种触控输入。当您开始在应用中支持触控时,应考虑多点触控可能会对应用产生哪些影响。
如果您一直在构建主要由鼠标驱动的应用,那么您可能习惯于最多使用一个光标点进行构建 - 系统通常不支持多个鼠标光标。对于许多应用,您只需将触摸事件映射到单个光标接口,但我们见过的大多数桌面触摸输入硬件至少可以处理 2 个同时输入,大多数新硬件似乎至少支持 5 个同时输入。当然,在开发屏幕钢琴键盘时,您希望能够支持同时进行的多点触控输入。
当前实现的 W3C Touch API 没有 API 来确定硬件支持多少个接触点,因此您必须使用最准确的估算值来估算用户需要的接触点数量,当然,还要注意您在实践中看到的接触点数量并进行相应调整。例如,在钢琴应用中,如果您从未看到两个以上的接触点,则可能需要添加一些“和弦”界面。PointerEvents API 确实有一个 API 用于确定设备的功能。
润色
希望本文能为您提供一些关于在实现触控和鼠标互动时常见的挑战的指南。当然,比任何其他建议更重要的是,您需要在移动设备、平板电脑以及结合使用鼠标和触控功能的桌面设备环境中测试应用。如果您没有触摸板和鼠标硬件,请使用 Chrome 的“模拟触摸事件”功能来帮助您测试不同的场景。
遵循这些指南,不仅可以构建出富有吸引力的互动体验,还可以让这些体验同时适用于触摸输入、鼠标输入,甚至两种互动方式。