触控和鼠标

首次重聚

简介

近 30 年来,桌面计算体验一直以键盘和鼠标或触控板作为主要的用户输入设备为中心。然而,在过去十年中,智能手机和平板电脑带来了一种新的互动模式:触摸。随着支持触控的 Windows 8 设备的推出,以及支持触控的 Chromebook Pixel 的发布,触控现在已成为用户预期的桌面体验的一部分。其中最大的挑战之一是,打造既适用于触摸设备和鼠标设备的体验,又能在用户会同时使用两种输入法(有时同时使用)的设备上提供体验!

本文将帮助您了解如何将触摸功能内置于浏览器中、如何将这种新的界面机制集成到您现有的应用中,以及触摸操作如何与鼠标输入实现良好的运行效果。

Web 平台中的触摸状态

iPhone 是最早内置有专用触摸 API 的网络浏览器平台。其他一些浏览器供应商也创建了类似的 API 接口,这些接口可以兼容 iOS 实现(目前在“触摸事件 1”规范中进行了说明)。桌面设备上的 Chrome 和 Firefox、iOS 和 Chrome 上的 Safari、Android 上的 Android 浏览器以及其他移动浏览器(例如黑莓浏览器)均支持触摸事件。

我的同事 Boris Smus 撰写了一篇非常精彩的 HTML5Rocks 关于触摸事件的教程,如果您之前未了解过触摸事件,该教程仍然是入门的不错方法。事实上,如果您之前未使用过触摸事件,请先阅读这篇文章,然后再继续操作。快,我等。

全部完成了?现在您已经对触摸事件有了基本的基础,编写支持触摸交互的挑战在于,触摸交互可能与鼠标(以及鼠标模拟触控板和轨迹球)事件有很大不同。虽然触摸界面通常会尝试模拟鼠标,但模拟并不完美或完成;您确实需要处理这两种交互样式,并且可能必须独立支持每种界面。

最重要的是:用户可能会触摸和使用鼠标

许多开发者构建的网站可以静态检测环境是否支持触摸事件,然后假设它们只需要支持触摸(而非鼠标)事件。现在,这是一个错误的假设,只是因为存在触摸事件并不意味着用户主要使用该触摸输入设备。Chromebook Pixel 和某些 Windows 8 笔记本电脑等设备现在同时支持鼠标和触摸输入法,并且不久还将支持更多输入法。在这些设备上,用户很自然地会同时使用鼠标和触摸屏与应用进行交互,因此“支持触摸”与“不需要支持鼠标”是两码事。您不能将问题看作“我必须编写两种不同的互动风格并在它们之间切换”,您需要考虑这两种互动如何协同工作和独立工作。在 Chromebook Pixel 上,我经常使用触控板,但我也会伸手去触摸屏幕 - 在同一个应用或页面上,我会做目前感觉最自然的事情。另一方面,有些触摸屏笔记本电脑用户很少会使用触摸屏,因此轻触输入的存在不应禁用或阻碍鼠标控制。

遗憾的是,我们很难判断用户的浏览器环境是否支持触控输入;理想情况下,桌面设备上的浏览器应始终表明支持触摸事件,以便随时连接触摸屏显示屏(例如,当可通过 KVM 连接触摸屏时)。出于所有这些原因,您的应用不应尝试在触摸和鼠标之间切换,只需同时支持这两种操作即可!

支持鼠标和触摸功能

#1 - 点击和点按 - 事物的“自然”顺序

第一个问题是触摸界面通常会尝试模拟鼠标点击,显然,因为触摸界面需要在之前仅与鼠标事件交互的应用上运行!您可以将此方法用作快捷方式,因为无论用户是使用鼠标点击还是点按屏幕,“点击”事件都会继续触发。不过,这种快捷键存在几个问题。

首先,在设计更高级的触摸交互时必须小心:当用户使用鼠标时,它会通过点击事件进行响应,但当用户轻触屏幕时,同时会发生触摸和点击事件。对于单次点击,事件顺序为:

  1. 轻触开始
  2. 轻触移动
  3. 触屏
  4. 鼠标悬停
  5. mousemove
  6. 鼠标按下
  7. 鼠标上移
  8. click

这当然意味着,如果您正在处理触摸事件(例如 touchstart),则需要确保不会同时处理相应的 mousedown 和/或点击事件。如果您可以取消触摸事件(在事件处理脚本内调用 preventDefault()),则不会为触摸生成任何鼠标事件。触摸处理程序最重要的规则之一是:

不过,这也可以防止其他默认浏览器行为(例如滚动),虽然通常情况下,触摸事件是完全在处理程序中处理的,因此您需要停用默认操作。一般来说,您需要处理和取消所有触摸事件,或者避免使用适用于该事件的处理程序。

其次,当用户在移动设备上点按网页中的元素时,那些未设计用于移动互动的网页在触摸启动事件和处理鼠标事件(鼠标按下)之间至少会有 300 毫秒的延迟。使用 Chrome 可以完成此测试。在 Chrome 开发者工具中开启“模拟触摸事件”,以帮助在非触摸系统上测试触摸界面!

此延迟是为了让浏览器有时间确定用户是否正在执行其他手势,尤其是点按两次进行缩放。显然,在您希望对手指触摸做出即时响应的情况下,这可能会出现问题。我们正在不断努力,力求避免自动发生此类延迟的情况。

Chrome(Android 版) Android 浏览器 Opera Mobile for Android) Android 版 Firefox iOS 版 Safari
不可缩放的视口 无延迟 300 毫秒 300 毫秒 无延迟 300 毫秒
无视口 300 毫秒 300 毫秒 300 毫秒 300 毫秒 300 毫秒

避免这种延迟的最简单方式是“告知”移动浏览器,说明无需缩放您的网页,具体可利用固定视口实现,例如将图片插入网页中:

<meta name="viewport" content="width=device-width,user-scalable=no">

当然,这并不一定适合这样做 - 这会停用双指张合缩放,这可能会因可访问性原因而需要,因此请谨慎使用(如果您停用用户缩放,则可能需要提供其他方式来提高应用中文本的可读性)。此外,对于支持触摸的桌面设备类设备上的 Chrome 和移动平台上的其他浏览器,如果网页包含不可缩放的视口,此延迟不适用

#2:Mousemove 事件不由触摸触发

此时,请务必注意,触摸界面中的鼠标事件的模拟通常不会扩展到模拟 mousemove 事件。因此,如果您构建一个使用 mousemove 事件的精美鼠标驱动的控件,该控件可能无法用于触摸设备,除非另外专门添加 touchmove 处理程序。

浏览器通常会针对 HTML 控件上的触摸交互自动实现适当的交互,因此,举例来说,HTML5 范围控件仅在您使用触摸交互时才能发挥作用。但是,如果您已经实现了自己的控件,那么这些控件可能不适用于点击并拖动类型的交互;事实上,一些常用的库(如 jQueryUI)目前还无法以这种方式原生支持触摸交互(尽管对于 jQueryUI,有多个 Monkey 补丁修复了此问题)。这是我在升级 Web Audio Playground 应用以使用触摸功能时遇到的第一个问题之一:滑块是基于 jQueryUI 的,因此它们不适用于点击并拖动互动。我改为使用 HTML5 Range 控件,然后它们可以正常运行。当然,我也可以只添加 touchmove 处理程序来更新滑块,但那里有一个问题...

#3:Touchmove 和 MouseMove 不同

我看到一些开发者会遇到一个问题,那就是将 touchmove 和 mousemove 处理程序调用相同的代码路径。这些事件的行为非常接近,但又略有不同 - 具体而言,触摸事件始终将触摸“STARTED”事件定位到元素,而鼠标事件则定位到当前位于鼠标光标下方的元素。这就是为什么我们有鼠标悬停事件和鼠标移开事件,但没有对应的触摸事件和触摸事件,只有 touchend。

导致此问题的最常见方式是,当您碰巧移除(或移动)用户开始触摸的元素时。例如,假设某个图片轮播界面在整个轮播界面中带有触摸处理程序,以支持自定义滚动行为。随着可用图片的变化,您移除一些 <img> 元素并添加其他元素。如果用户恰好开始触摸其中一张图像,然后您将其移除,您的处理程序(位于 img 元素的祖先上)将停止接收触摸事件(因为触摸事件被分派到不再位于树中的目标)- 看起来用户就像是在某个位置按住手指,即使他们可能已经移动并最终将其移除。

当然,您可以避免此问题,因为当触摸处于活动状态时,应避免移除具有(或具有)触摸处理程序的祖先元素。另外,最好不要注册静态 touchend/touchmove 处理程序,等到收到 touchstart 事件后再将 touchmove/touchend/touchcancel 处理程序添加到 touchstart 事件的 target(并在结束/取消时将其移除)。这样,即使目标元素移动/移除,您也会继续接收触摸事件。您可以在此处尝试操作 - 轻触红色方框,同时按住点击退出键将其从 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> 元素中,使其也成为制表符,这样一来,用户无需 JavaScript 即可切换鼠标指针、触摸或按键操作的额外信息。我非常惊喜地发现,当我开始着手让 Web Audio Playground 能与触摸界面完美搭配时,弹出式菜单在触摸方面的表现也不错,因为我使用了这种结构!

上述方法非常适用于基于鼠标指针的接口以及触摸界面。这与在光标悬停时使用“标题”属性相反,后者不会在元素激活时显示:

<img src="/awesome.png" title="this doesn't show up in touch">

#5:触摸精度与鼠标精度

虽然鼠标在概念上与现实脱节,但事实证明它们非常准确,因为底层操作系统通常会跟踪光标的精确像素精度。另一方面,移动开发者已经了解到,手指在触摸屏上的触摸并不那么准确,这主要是因为手指接触屏幕时的表面积大小所致(部分原因是手指遮挡了屏幕)。

许多个人和公司就如何设计可支持手指交互的应用和网站进行了大量的用户研究,并且已经有人撰写了许多有关该主题的书籍。基本建议是通过增加内边距来增加触摸目标的大小,并增加元素之间的外边距来降低点按错误的可能性。(在对触摸和点击事件的点击检测处理中,不包含外边距,但内边距包括在内。)我要对 Web Audio Playground 进行的主要修正之一是增加连接点的大小,以便能更轻松地触摸它们。

许多处理触摸界面的浏览器供应商还向浏览器中引入了逻辑,以帮助在用户触摸屏幕时定位正确的元素,并降低点击错误的可能性。不过,这通常只能更正点击事件,而不会更正移动事件(尽管 Internet Explorer 似乎也会修改鼠标按下/mousemove/mouseup 事件)。

#6:确保触摸处理程序包含在内,否则它们会卡顿您的滚动

此外,让触摸处理程序仅限于您需要它们的元素也很重要;触摸元素可能具有非常高的带宽,因此避免在滚动元素上使用触摸处理程序(因为您的处理可能会干扰浏览器优化以实现快速无卡顿的触摸滚动 - 现代浏览器会尝试在 GPU 线程上滚动,但如果它们必须先使用 JavaScript 检查应用是否处理每个触摸事件,则无法这样做)。您可以查看此行为的示例

为避免此问题,需要遵循的一项指导原则是,如果您仅在界面的一小部分中处理触摸事件,则仅将触摸处理程序附加在此处(而不是在页面的 <body> 上附加);简而言之,应尽可能限制触摸处理程序的范围。

#7:多点触控

最后一个有趣的挑战是,尽管我们称之为“触摸”界面,但实际上对多点触控界面的支持几乎普遍存在,也就是说,API 一次提供多个触控输入。开始在应用中支持触摸功能时,您应考虑多次触摸可能会如何影响您的应用。

如果您构建的应用主要由鼠标驱动,那么您也习惯于使用最多一个光标进行构建,因为系统通常不支持多个鼠标光标。对于许多应用,您只需将触摸事件映射到单个光标界面,但是我们看到的桌面触摸输入大多数硬件都可以处理至少 2 个同步输入,而大多数新硬件似乎支持至少 5 个同时输入。当然,如果您要开发屏幕钢琴键盘,最好能够支持多个同时触控输入。

当前实现的 W3C Touch API 没有 API 可以确定硬件支持的接触点数量,因此您必须利用最佳估算来判断用户需要的接触点数量,当然,也要注意您在实践中看到的接触点数量,并做出调整。例如,在钢琴应用中,如果您从没有看到超过两个接触点,则可能需要添加一些“和弦”界面。PointerEvents API 确实具有用于确定设备功能的 API。

修饰

希望本文能帮助您针对实现触摸和鼠标互动时遇到的常见挑战提供一些指导。当然,比任何其他建议更重要的是,您需要在移动设备、平板电脑以及鼠标和触摸组合的桌面环境中测试您的应用。如果您没有“触摸+鼠标”硬件,请使用 Chrome 的“模拟触摸事件”来帮助您测试不同的场景。

遵循这些指导原则,打造出引人入胜的互动体验,可与触控输入、鼠标输入,甚至是同时采用这两种互动方式完美搭配,是可以的,而且相对容易。