简介
诸如智能手机和平板电脑的移动设备通常会有一个电容式触摸屏,用于捕捉用户的手指所做的交互操作。移动网络在不断的发展之中,能够支持越来越复杂的应用, 网络开发者需要找到一种方法来处理这些事件。例如,几乎所有的快节奏游戏都需要玩家同时按下多个按钮,这种方式对于触摸屏来说就意味着多点触摸。
Apple 公司在 iOS 2.0 中引入了触摸事件 API,Android 正迎头赶上这一现行标准,并缩小差距。最近,一个 W3C 工作组正在合力制定这一触摸事件规范。
在本文中,我会深入说明 iOS 和 Android 设备提供的触摸事件 API,探索可以构建哪些类型的应用程序,给出一些最佳做法,并论及一些能够简化触控应用程序开发的实用技术。
触摸事件
三种在规范中列出并跨移动设备广泛实现的基本触摸事件:
- touchstart:手指放在一个 DOM 元素上。
- touchmove:手指拖动一个 DOM 元素。
- touchend:手指从一个 DOM 元素上移开。
每个触摸事件都包括了三个触摸列表:
- touches:当前位于屏幕上的所有手指动作的列表。
- targetTouches:位于当前 DOM 元素上的手指动作的列表。
- changedTouches:涉及当前事件的手指动作的列表。例如,在一个 touchend 事件中,这将是移开手指。
这些列表由包含触摸信息的对象组成:
- identifier:一个数字,用于唯一标识触摸会话中的当前手指。
- target:作为动作目标的 DOM 元素。
- 客户端/页面/屏幕坐标:动作在屏幕上发生的位置。
- 半径坐标和 rotationAngle:画出与手指形状类似的椭圆形。
可触控的应用程序
touchstart、touchmove 和 touchend 事件提供了一组足够丰富的功能,可支持几乎任何类型的基于触摸的交互,其中包括所有常见的多点触控手势(如开合缩放、旋转等)。
下面这段代码可让您使用单指触摸随意拖动一个 DOM 元素:
var obj = document.getElementById('id'); obj.addEventListener('touchmove', function(event) { // If there's exactly one finger inside this element if (event.targetTouches.length == 1) { var touch = event.targetTouches[0]; // Place element where the finger is obj.style.left = touch.pageX + 'px'; obj.style.top = touch.pageY + 'px'; } }, false);
下面的示例显示了屏幕上当前所有的触点,其作用就是用来感受一下设备的响应性。
// Setup canvas and expose context via ctx variable canvas.addEventListener('touchmove', function(event) { for (var i = 0; i < event.touches.length; i++) { var touch = event.touches[i]; ctx.beginPath(); ctx.arc(touch.pageX, touch.pageY, 20, 0, 2*Math.PI, true); ctx.fill(); ctx.stroke(); } }, false);
演示
有趣的多点触控演示随处可见,例如这个由保罗·爱丽诗 (Paul Irish) 和其他一些人制作的基于画布的绘画演示。
还有浏览器忍者(Browser Ninja),这是一个使用 CSS3 转换、过渡和画布制作的仿“水果忍者”(Fruit Ninja) 技术演示:
最佳做法
禁止缩放
多点触控的默认设置不是很好用,因为您的滑动和手势往往与浏览器的行为有关联,例如滚动和缩放。
要停用缩放,应使用下面的元标记设置视口,这样用户就无法扩展视口了:
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
有关设置视口的详情,请参阅这篇关于移动 HTML5 的文章。
禁止滚动
某些移动设备对于 touchmove 有默认的行为,例如经典的 iOS overscroll 特效,会在滚动超出内容的界限时引发视图反弹。这种做法在很多的多点触控应用程序中会带来混乱,不过很容易就能停用:
document.body.addEventListener('touchmove', function(event) { event.preventDefault(); }, false);
仔细渲染
如果您要编写的多点触控应用程序涉及到复杂的多指手势,应仔细考虑如何响应触摸事件,因为需要一次处理很多的事件。请考虑前一章节中在屏幕上绘制所有触点的例子,您可以在有触摸输入的时候就立刻进行绘制:
canvas.addEventListener('touchmove', function(event) { renderTouches(event.touches); }, false);
不过这一技术并不是要随着屏幕上的手指数增多而扩展。您可以改为跟踪所有手指,然后在一个循环中进行渲染,这样获得的性能要好得多:
var touches = [] canvas.addEventListener('touchmove', function(event) { touches = event.touches; }, false); // Setup a 60fps timer timer = setInterval(function() { renderTouches(touches); }, 15);
提示:setInterval 不太适于动画,因为它没有考虑到浏览器自身的渲染循环。现代的桌面浏览器提供了 requestAnimationFrame,从性能和电池寿命方面考虑,这是一个更好的选择。因此只要移动浏览器支持这一方式,就应优先选择。
利用 targetTouches 和 changedTouches
要记住的一点是,event.touches 是与屏幕接触的所有手指的数组,而不仅仅是位于目标 DOM 元素上的那些。您可能会发现,改用 event.targetTouches 或 event.changedTouches 要更实用一些。
最后,由于您是针对移动设备进行开发,应该注意移动的最佳做法,这些在埃里克·比德尔曼 (Eric Bidelman) 的文章 以及这篇 W3C 文档中有所论及。
设备支持
很遗憾,触摸事件的实现在完备性和质量方面的差别很大。我编写了一个诊断脚本, 用于显示一些关于触摸 API 实现的基本信息,其中包括哪些事件是支持的,以及 touchmove 事件触发的解决方案。我在 Nexus One 和 Nexus S 硬件上测试了 Android 2.3.3,在 Xoom 上测试了 Android 3.0.1,还在 iPad 和 iPhone 上测试了 iOS 4.2。
简而言之,测试的所有浏览器都支持 touchstart、touchend 和 touchmove 事件。
规范提供了另外三个触摸事件,但测试的浏览器都不支持它们:
- touchenter:移动的手指进入一个 DOM 元素。
- touchleave:移动手指离开一个 DOM 元素。
- touchcancel:触摸中断(实现规范)。
测试的浏览器还在每个触摸列表内提供了 touches、targetTouches 和 changedTouches 触摸列表。不过,测试的浏览器都不支持 radiusX、radiusY 或 rotationAngle(这些属性指定了触摸屏幕的手指的形状)。
在一次 touchmove 期间,所有的测试设备都是一秒钟大约触发 60 次事件。
Android 2.3.3 (Nexus)
Android 的 Gingerbread 浏览器(在 Nexus One 和 Nexus S 上进行了测试)不支持多点触控。这是一个已知问题。
Android 3.0.1 (Xoom)
Xoom 的浏览器对多点触控提供了基本的支持,不过只能在单个 DOM 元素上起作用。浏览器不能正确响应同时发生在不同 DOM 元素上的两个触摸事件,换句话说,下面的代码会对两个同时发生的触摸事件进行响应:
obj1.addEventListener('touchmove', function(event) { for (var i = 0; i < event.targetTouches; i++) { var touch = event.targetTouches[i]; console.log('touched ' + touch.identifier); } }, false);
但下面的代码则不会:
var objs = [obj1, obj2]; for (var i = 0; i < objs.length; i++) { var obj = objs[i]; obj.addEventListener('touchmove', function(event) { if (event.targetTouches.length == 1) { console.log('touched ' + event.targetTouches[0].identifier); } }, false); }
iOS 4.x(iPad、iPhone)
iOS 设备完全支持多点触控,能够跟踪多个手指,并在浏览器中提供高灵敏度的触摸体验。
开发人员工具
在移动开发中,较为容易的做法是先在桌面上开始原型设计,然后再在要支持的设备上处理专门针对移动设备的部分。而多点触控正是难以在 PC 上进行测试的功能之一,因为大部分的 PC 都没有触摸输入功能。
由于不得不在移动设备上进行测试,这样可能会拉长您的开发周期,因为您所做的每项更改都需要提交到服务器,然后再加载到设备上。而一旦运行后,对于应用程序也就没法进行太多调试了,因为平板电脑和智能手机都缺乏网络开发人员所用的工具。
对于这个问题,一种解决方案就是在开发机器上模拟触发事件。对于单点触摸,触摸事件可以基于鼠标事件来模拟。如果您有支持触摸输入的设备(如时下流行的 Apple MacBook),那么也可以模拟多点触控。
单点触控事件
如果您想在桌面上模拟单点触控事件,请尝试使用 Phantom Limb,它可以在网页上模拟触摸事件并提供一只巨手进行引导。
另外还有 Touchable 这一 jQuery 插件,可跨平台地统一触摸和鼠标事件。
多点触控事件
为了能够让您的多点触控网络应用程序在浏览器或多点触控板(如 Apple MacBook 或 MagicPad)上正常使用,我创建了 MagicTouch.js 填充工具,用于捕捉来自触控板的触摸事件,然后将其转换成标准兼容的触摸事件。
- 下载 npTuioClient NPAPI 插件 并安装到 ~/Library/Internet Plug-Ins/ 目录下。
- 下载适用于苹果机的 MagicPad 的 TongSeng TUIO 应用程序,并启动服务器。
- 下载 MagicTouch.js,这是一个 JavaScript 库,可根据 npTuioClient 回调来模拟规范兼容的触摸事件。
- 以如下方式将 magictouch.js 脚本和 npTuioClient 插件添加到您的应用程序中:
<head> ... <script src="/path/to/magictouch.js"></script> </head> <body> ... <object id="tuio" type="application/x-tuio" style="width: 0px; height: 0px;"> Touch input plugin failed to load! </object> </body>
我只在 Chrome 浏览器 10 上测试了这一方法,不过只要稍做调整就应该能够适用于其他的现代浏览器。
如果您的计算机没有多点触控输入功能,可以使用其他 TUIO 跟踪器(例如 reacTIVision)来模拟触摸事件。有关详情,请参阅 TUIO 项目网页。
请注意,您的手势可能与操作系统级别上的多点触控手势相同。在 Mac OS X 上,您可以在“系统偏好设置”(System Preferences) 的“触控板”(Trackpad) 偏好设置面板中配置系统范围的事件。
随着多点触控功能逐渐得到越来越广泛的跨移动浏览器支持,我非常高兴地看到新的网络应用程序充分利用这一丰富的 API。