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