2009年11月24日星期二

编写 iPhone Friendly 的 Web 应用程序 (Part 7 - 多点触击)

这个系列的上一篇文章差不多是两年之前的事情了,在这两年里Mobile Safari并非停滞不前,从iPhone 2.0开始Mobile Safari就加入了对多点触击的支持,现在我们就来看一下我们可以利用它来干什么。

相信很多人都看过WPF为Surface设备做的一个简单demo,也就是在桌面上显示若干张照片,你可以通过单点触击拖放,也可以通过多点触击缩放和旋转。这在iPhone上能够做到,甚至在Mobile Safari里面也能做到,因为Mobile Safari提供了一套专门用于多点触击的JavaScript接口。现在我们就来看看如何利用这套接口吧。

我们都知道,Mobile Safari自身会处理多点触击,默认行为包括滚动和缩放。我们可以接管相应的事件,同时使用e.preventDefault()禁用浏览器默认行为,使得我们的Web应用程序能够如同WPF桌面应用一样处理多点触击。下面我们来深入看看Mobile Safari提供的多点触击事件。

单点触击

首先我们要处理的是单点触击事件,禁用浏览器的滚动行为,同时为我们的照片(一个img元素)增加拖动行为。在这里,我们需要用到touchstarttouchmovetouchend事件。在这三个事件里,我们可以通过e.targetTouches获取到用户点击的坐标,从而计算相对的位置变化。

首先,我们要在touchstart事件里面记录下初始坐标:

var transform = {
  x: 0,
  y: 0,
  rotation: 0,
  scale: 1
};

var startX;
var startY;
var touching = false;

element.addEventListener("touchstart", function(e){
  e.preventDefault();
  startX = e.targetTouches[0].clientX;
  startY = e.targetTouches[0].clientY;
  touching = true;
});


接着,我们要在touchmove事件里面计算相对位置变化,并且更新element坐标:

element.addEventListener("touchmove", function(e){
  e.preventDefault();
  if (!touching) return;
  transform.x += e.targetTouches[0].clientX - startX;
  transform.y += e.targetTouches[0].clientY - startY;
  updateTransform();
  startX = e.targetTouches[0].clientX;
  startY = e.targetTouches[0].clientY;
});
updateTransform做了什么?现在先不讨论,我们只要把事件相关数据正确地更新到transform的四个属性即可,如何把这些属性反映到界面上稍后再说。

最后,我们还要在touchend事件里面处理一下标志位:

element.addEventListener("touchend", function(e){
  e.preventDefault();
  touching = false;
)};


就这么简单?是的。关键点也就在于touchmove时跟踪e.targetTouches的变化,并更新transform里面的信息。

CSS3变换

接下来我们看看如何将transform里面的信息作用到界面上。在没有CSS3的时代,这是极之痛苦的事情,我们需要修改元素的多个样式属性才能实现这部分的功能,并且还没办法实现旋转。现在有了CSS3,只需要修改一下transform属性就可以了:

var updateTransform = function(){
  element.style.webkitTransform
    = "translate(" + transform.x + "px, "
    + transform.y + "px) "
    + "rotate(" + transform.rotation + "deg) "
    + "scale(" + transform.scale + ")";
}


一句代码就把位置、旋转、缩放都设置好了!尽管我们现在还没用到旋转和缩放属性,那就让它们保持默认值吧,我们在多点触击的事件里面会设置它们的。

多点触击

多点触击涉及到三个事件:gesturestartgesturechangegestureend。这三个事件跟单点触击的三个事件非常类似,使用起来甚至可以说是更简单一些:

var startRotation;
var startScale
var gesturing = false;

element.addEventListener("gesturestart", function(e){
  e.preventDefault();
  startRotation = transform.rotation;
  startScale = transform.scale;
  gesturing = true;
});

element.addEventListener("gesturechange", function(e){
  e.preventDefault();
  if (!gesturing) return;
  transform.rotation = startRotation + e.rotation;
  transform.scale = startScale * e.scale;
  updateTransform();
});

element.addEventListener("gestureend", function(e){
  e.preventDefault();
  gesturing = false;
});


代码确实比之前的还要少一些,重点就是正确设置transform的两个属性,随后调用一下updateTransform就能把最近的状态更新的界面上。

小结

在这篇文章里,我们了解到了Mobile Safari的6个特有事件,以及如何利用这6个特有事件处理多点触击。

如果你直接使用我的代码去实现开头所说的照片拖放应用,你会发现一个小问题——在进行多点触击操作时,旋转与缩放都是很自然的,就是拖动不自然,好像拖动只跟随第一个触点似的。原因很简单,在多点触击时,管理触点移动的还是touchmove事件,但上述代码只处理e.targetTouches[0],所以拖动只跟随第一个触点。

如果需要同时跟随两个触点,你需要对代码稍作改动,使得移动距离为e.targetTouches[0]e.targetTouches[1]的平均值。为什么呢?如果一个触点往上移动30px,另一个触点往下移动10px,除去旋转与缩放效果外,照片的中点应该是往上移动10px的,也就是两个移动的平均值。那么我如何知道当前有多少个触点呢?看看e.targetTouches.length就知道了。

最后,如果你关注移动设备上的Web开发,欢迎订阅我的博客:

没有评论:

发表评论