多点触摸网络开发

HTML5 Rocks

简介

诸如智能手机和平板电脑的移动设备通常会有一个电容式触摸屏,用于捕捉用户的手指所做的交互操作。移动网络在不断的发展之中,能够支持越来越复杂的应用,网络开发者需要找到一种方法来处理这些事件。例如,几乎所有的快节奏游戏都需要玩家同时按下多个按钮,这种方式对于触摸屏来说就意味着多点触摸。

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:画出与手指形状类似的椭圆形。

可触控的应用程序

touchstarttouchmovetouchend 事件提供了一组足够丰富的功能,可支持几乎任何类型的基于触摸的交互,其中包括所有常见的多点触控手势(如开合缩放、旋转等)。

下面这段代码可让您使用单指触摸随意拖动一个 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。

简而言之,测试的所有浏览器都支持 touchstarttouchendtouchmove 事件。

规范提供了另外三个触摸事件,但测试的浏览器都不支持它们:

  • touchenter:移动的手指进入一个 DOM 元素。
  • touchleave:移动手指离开一个 DOM 元素。
  • touchcancel:触摸中断(实现规范)。

测试的浏览器还在每个触摸列表内提供了 touchestargetToucheschangedTouches 触摸列表。不过,测试的浏览器都不支持 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 填充工具,用于捕捉来自触控板的触摸事件,然后将其转换成标准兼容的触摸事件。

  1. 下载 npTuioClient NPAPI 插件 并安装到 ~/Library/Internet Plug-Ins/ 目录下。
  2. 下载适用于苹果机的 MagicPad 的 TongSeng TUIO 应用程序,并启动服务器。
  3. 下载 MagicTouch.js,这是一个 JavaScript 库,可根据 npTuioClient 回调来模拟规范兼容的触摸事件。
  4. 以如下方式将 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。

Comments

0