现在,为智能触摸手机创建直观的用户界面时,最重要的部分不再是单纯的视觉效果,而是要创建出能很好地处理用户触摸交互的界面。对于Web应用而言,这意味着使用touch事件来取代传统的mouse事件。在Dojo 1.7中,新的touch API可以帮助您简化这一步骤。
本文是Touching and Gesturing on the iPhone的更新版,该文最早发布于2008年
引言
在我们讨论Dojo 1.7中那些帮助创建可触摸界面的新功能之前,先来了解下底层的技术、概念。Apple在发布iPhone的同时引入了两种新的事件概念:touch和gesture。Touch用来保存有多少手指接触在屏幕上以及这些触点的位置、触点的状态。Gesture则是用来描述用户在交互时到底做了哪些更高层次上的行为,比如:pinching,rotating,swiping,double-tapping等等。
Touch事件在众多平台上都得到支持(最初在iOS上建立起的touch事件模型已被作为标准写入W3C Touch Events specification),而原生的Gesture事件在很多地方都没有得到支持,在iOS上的Gesture事件也仅限于其平台支持的少量gesture。
Touches
当你将一个手指放到屏幕上时,将触发Touch事件的生命周期。每次手指触摸屏幕,一个新的touchstart事件将会产生。每次手指离开,一个touchend事件将会触发。如果你触摸屏幕并移动手指,那touchmove事件将会触发。如果有太多手指触摸屏幕或者有另一个行为(比如说手机操作系统的信息推送) 打断了touch,一个touchcancel 事件会被触发。
Touch事件列表:
- touchstart: 当手指触摸到屏幕时被触发
- touchend: 当手指离开屏幕时被触发
- touchmove: 当手指在屏幕上移动时被触发
- touchcancel: 当Touch被打断或是有太多手指触摸屏幕时被触发。
虽然看起来Touch事件和Mouse事件之间有一一对应的关系,手指移动就如同使用光标一样,但事实上TouchEvent对象中并不包括那些你可能希望看到的内容。比如,pageX和pageY属性并没有被赋值。这是因为在使用鼠标时,你只有一个交互:光标。但在多点触摸的设备上,你可以在屏幕的左边同时按下两根手指并在屏幕的右边用另一根手指进行点击,这三个接触点都会被系统注册。
为了一次提供所有接触点的信息,每一个TouchEvent对象有一个保存着每个触点信息的属性。同时,它还有另外两个属性:其中一个保存着由当前目标节点触发的触点信息,另一个仅保存着当前事件相关的触点信息。
- touches: 包含当前屏幕上每个触点信息的列表。
- targetTouches: 和touches类似,但只包含在触发该Touch事件的节点上的触点信息。
- changedTouches: 包含每个接触状态变化的触点信息的列表。
为了更好的理解这些列表,让我们来看一些例子。
- 当你将手指放到屏幕上,三个列表中的信息相同。
- 当你将第二根手指放到屏幕上,touches 将包含两个触点的信息。如果第二根手指放在第一根手指所在的节点上,targetTouches 将同样包含两个触点的信息,否则它将之包含第二个手指的触点信息。 changedTouches 将只包含第二个手指的触点信息,因为是由第二个手指的接触触发的此次Touch事件。
- 如果同时用两根手指接触屏幕,那changedTouches将包含着两个手指的触点信息。
- 如果你移动手指,唯一会发生变化的是changedTouches。它将包含所有移动手指的触点信息。
- 当你移开一根手指,它对应的触点信息将被从touches和targetTouches中移除,并会被添加到changedTouches列表中。
- 当你移开最后一根手指,touches和targetTouches列表将被清空,并且changedTouches将只包含最后一根手指的触点信息。
使用这些列表,可以比较清楚的了解用户到底在进行那些操作。想象一下做一个JavaScript版的超级玛丽——你会需要知道玩家在按哪个方向键,而在玩家想要发射火球或者跳跃时,你需要监控玩家到底是按在哪个虚拟的按钮上。
现在我们已经讨论了保存屏幕上手指触点信息的几个列表,但我们还没有谈到这些触点信息到底有哪些。这些触点信息包含一些和MouseEvent对象中类似的信息。下面是这些具体信息内容的列表:
- clientX: 触点相对于viewport的X坐标(不包括页面滚动的偏移量)
- clientY: 触点相对于viewport的Y坐标(不包括页面滚动的偏移量)
- screenX: 相对屏幕的X坐标
- screenY: 相对屏幕的Y坐标
- pageX: 触点相对于整个页面的X坐标 (包括页面滚动的偏移量)
- pageY: 触点相对于整个页面的Y坐标 (包括页面滚动的偏移量)
- identifier: 数字ID编号,用以区别每个触点。
- target: 触点所在的DOM节点
为智能手机开发Web应用时很烦人的一点是即使你为你的应用设置好了viewport,在屏幕上移动你的手指可能会移动整个页面。幸运的是,touchmove事件对象有一个preventDefault方法,可以被用来阻止页面被移动。
使用Touch API实现拖拽
在触摸设备上实现拖拽功能非常方便,因为touchmove事件只有一个手指已经触摸在了屏幕上才会被触发。这意味着我们不需要像使用mousemove事件那样来监控鼠标按键的状态。下面是基本的拖拽功能的实现:
[javascript] view plaincopy
- node.addEventListener("touchmove", function(event){
- // Only deal with one finger
- if(event.touches.length == 1){
- // Get the information for finger #1
- var touch = event.touches[0],
- // Find the style object for the node the drag started from
- style = touch.target.style;
- // Position the element under the touch point
- style.position = "absolute";
- style.left = touch.pageX + "px";
- style.top = touch.pageY + "px";
- }
- }, false);
Dojo 1.7中更好的Touch
使用底层Touch事件有一个问题,如果你正在创建的应用需要可以运行在触摸设备及使用鼠标的设备上,你将不得不使用两套event listener。Dojo 1.7中新的dojo/touch模块标准化了Touch和Mouse事件,在支持触摸的设备上使用Touch事件机制,在其它设备上自动蜕化成使用Mouse事件,从而提供了一套设备无关的事件机制。这套机制使用起来和监听普通的事件一样简单,你只需将传统的事件名替换成一些事件函数:
[javascript] view plaincopy
- require([ "dojo", "dojo/touch" ], function(dojo, touch){
- dojo.connect(dojo.byId("myElement"), touch.press, function(event){
- // handle a mousedown/touchstart event
- });
- dojo.connect(dojo.byId("myElement"), touch.release, function(event){
- // handle a mouseup/touchend event
- });
- });
Gestures
在iOS设备上,一个Gesture事件在两个或更多手指触摸屏幕时被触发。如果任何手指落在你正监听Gesture事件(gesturestart, gesturechange,gestureend)的节点上,你将收到对应的Gesture事件。
Gesture事件提供一个GestureEvent对象,该对象包含以下属性:
- rotation: 使用手指旋转的角度。
- scale: 用户使用手指进行pinch和push操作时产生的一个倍数。如果大于1,用户则在进行push操作,小于1,则用户在进行pinch操作。
当同时监听了Gesture事件和Touch事件,则事件触发模式如下:
- touchstart 第一个手指接触屏幕时触发
- gesturestart 第二个手指接触屏幕时触发
- touchstart 第二个手指接触屏幕时触发
- gesturechange 两个手指都在屏幕上时,每次手指在屏幕上移动时触发
- gestureend 第二个手指 离开屏幕时触发
- touchend 第二个手指 离开屏幕时触发
- touchend 第一个手指 离开屏幕时触发
Resizing and rotating with the Gestures API
使用CSStransform, width, 和height属性,我们可以很简单地使用这些Gesutre事件来缩放,旋转任何元素。
[javascript] view plaincopy
- var width = 100,
- height = 200,
- rotation = 0;
- node.addEventListener("gesturechange", function(event){
- var style = event.target.style;
- // scale and rotation are relative values,
- // so we wait to change our variables until the gesture ends
- style.width = (width * event.scale) + "px";
- style.height = (height * event.scale) + "px";
- style.webkitTransform = "rotate(" + ((rotation
- + event.rotation) % 360) + "deg)";
- }, false);
- node.addEventListener("gestureend", function(event){
- // Update the values for the next time a gesture happens
- width *= event.scale;
- height *= event.scale;
- rotation = (rotation + event.rotation) % 360;
- }, false);
Dojo 1.7中更好的Gesture
Dojo 1.7包含了一个新的包dojox/gesture,它提供了处理触摸设备上更复杂的Gesture的能力。其中dojox/gesture/Base模块定义了一套可扩展出自定义Gesture的框架,除此之,该包还提供了一些常用Gesture的支持,如tap,tap and hold,double tap,and swipe。
使用dojox gesture非常简单。就像使用dojo/touch一样,为了监听某一个Gesture,你只需使用dojo.connect绑定一个Gesture,将传统的事件名替换为一个Gesture函数:
[javascript] view plaincopy
- require([ "dojo", "dojox/gesture/swipe", "dojox/gesture/tap" ],
- function(dojo, swipe, tap){
- dojo.connect(dojo.byId("myElement"), swipe, function(event){
- // handle swipe event
- });
- dojo.connect(dojo.byId("myElement"), tap.doubletap, function(event){
- // handle double tap event
- });
- });
将来,dojox/gesture将包含更多的Gesture种类,比如pinching和zooming。现在它提供了几种原本难以处理的事件的支持,以及一套跨平台可扩展的Gesture事件框架。