移动端的多点触控,你了解了吗

对于PC端,我们的点击事件比较简单,因为鼠标指针只有一个。但是对于移动端来说,存在多个接触点同时操作的情况,这种情况就是我们今天要研究的主题–多点触控。

接触点类Touch

在介绍之前,我们必须要了解这个构造方法。它代表的是屏幕上接触的点。它的属性包含了点的唯一标识(ID)、点的坐标、点的容纳区、以及接触点的压力大小等。具体属性值如下:

identifier
接触点的唯一标识,每个接触点的id在接触过程中都是唯一的
clientX:0
clientY:0
pageX:0
pageY:0
screenX:0
screenY:0
接触点的位置信息,跟pc端一样
force:0
接触点的压力值大小 0.0-1.0之间
radiusX:0
radiusY:0
rotationAngle:0
能包含接触点的最小椭圆
radiusX为椭圆x轴长
radiusY为椭圆y轴长
rotationAngle为椭圆旋转角度
target:dom
接触点接触的HTML对象

读者可以看出,上面这个类,准确的定义了一个接触点的各种属性。那么,在哪才能得到这些接触点的对象呢?

聪明如你,估计已经猜到了,那就是在触摸事件中。

触摸事件中的接触点对象

常用的触摸事件有touchestart、touchmove、touchend、touchcacel。它们都是TouchEvent类的实例,TouchEvent类的属性包含了前面介绍的接触点Touch实例列表。我们主要关注它的三个属性,如下:

TouchEvent.changedTouches 只读
一个 TouchList 对象,包含了代表所有从上一次触摸事件到此次事件过程中,状态发生了改变的触点的 Touch 对象。
TouchEvent.touches 只读
一 个 TouchList 对象,包含了所有当前接触触摸平面的触点的 Touch 对象,无论它们的起始于哪个 element 上,也无论它们状态是否发生了变化。
TouchEvent.targetTouches 只读
一个 TouchList 对象,是包含了如下触点的 Touch 对象:触摸起始于当前事件的目标 element 上,并且仍然没有离开触摸平面的触点。

完整的属性列表,可以参考MDN文档

上面这段介绍,我们可以看出,在触摸的整个过程中,我们都可以找到接触点的一个引用,以此,我们可以结合Touch对象的ID来对每个触摸进行追踪。

对多点触控的追踪

下面我们就通过代码来交流了,我们要实现一个接触点追踪的示例程序,主要功能是记录每个接触的生命周期。

这里的生命周期是指,一个接触点从touchstart开始到touchend结束,以及这之间所发生的touchmove事件。

首先,我们定义一个数据结构,用来表示生命周期的开始和结束。这个类需要有唯一的ID表示一个生命周期,需要有开始的touch事件,结束的touch事件,以及在生命周期内的状态变化,此处为touchmove的变化。

所以,大致如下:

/**
 * touch事件的生命周期类
 * @constructor
 * */
var TouchLife = (function () {
   var id = 0;
   return function () {
      /**
       * 生命周期的id
       * @type {number}
       * */
      this.id=id++;

      /**
       * 开始的touch事件
       * @type {Touch}
       * */
      this.startTouchEvent = null;

      /**
       * 结束的touch事件
       * @type {Touch}
       * */
      this.endTouchEvent = null;

      /**
       * 结束的touch事件
       * @type {Touch[]}
       * */
      this.moveTouchEventList = [];
   };
})();

有了生命周期的数据结构,我们还需要一个追踪器,用来追踪某个dom元素的touch生命周期。

追踪器的主要功能呢应该能够记录所有的生命周期,并且在某个生命周期状态变化的时候,应该能通知应用程序做处理。

这里直接给出它的实现:

/**
 * touch事件追踪器
 * @param opt
 * @param opt.target   被追踪的dom对象
 * @constructor
 */
function TouchLifeTracer(opt) {
   /**
    * 追踪的对象
    * */
   this.target = opt.target;

   /**
    * 作用于target的所有生命周期,包含存活和死亡的周期
    * */
   this._lifeList = [];

   /**
    * 当前存活的生命周期,正在与target接触的触摸点生命周期
    * */
   this.currentLifeList = [];

   /**
    * 某个生命周期开始
    * @type {function}
    * @param callback(life)
    * */
   this.onlifestart = null;

   /**
    * 某个生命周期状态变更
    * @type {function}
    * @param callback(life)
    * */
   this.onlifechange = null;

   /**
    * 某个生命周期开始
    * @type {function}
    * @param callback(life)
    * */
   this.onlifeend = null;

   /**
    * 添加生命周期
    * @param life {TouchLife} 生命周期
    * @return {*}
    */
   this.addLife = function (life) {
      this._lifeList.push(life);
   };

   /**
    * 根据identifier查找生命周期,此方法只能在生命周期内使用
    * @param identifier
    * @return {*}
    */
   this.findCurrentLifeByTouchID = function (identifier) {
      for(var i=0;i<this.currentLifeList.length;i++){
         var life = this.currentLifeList[i];
         if(life.startTouchEvent.identifier===identifier)
            return life;
      }
   };

   /**
    * 根据touchID删除当前触摸的生命周期
    * @param identifier
    * @return {boolean}
    */
   this.deleteCurrentLifeByTouchID = function (identifier) {
      for(var i=0;i<this.currentLifeList.length;i++){
         var life = this.currentLifeList[i];
         if(life.startTouchEvent.identifier===identifier){
            this.currentLifeList.splice(i,1);
            return true;
         }
      }
      return false;
   };


   /**
    * 初始化
    */
   this.init = function () {
      var self = this;
      this.target.addEventListener("touchstart",function (e) {
         e.preventDefault();
         var touchLife = new TouchLife();
         touchLife.startTouchEvent = e.changedTouches[0];
         self.addLife(touchLife);
         self.currentLifeList.push(touchLife);
         self.onlifestart && self.onlifestart(life);
      });

      this.target.addEventListener('touchmove',function (e) {
         e.preventDefault();
         var touches = e.changedTouches;
         for(var i=0;i<touches.length;i++){
            var touch = touches[i];
            var life = self.findCurrentLifeByTouchID(touch.identifier);
            life.moveTouchEventList.push(touch);
            self.onlifechange && self.onlifechange(life);
         }
      });
      this.target.addEventListener('touchend',function (e) {
         e.preventDefault();
         var touch = e.changedTouches[0];
         var life = self.findCurrentLifeByTouchID(touch.identifier);
         life.endTouchEvent = touch;
         self.deleteCurrentLifeByTouchID(touch.identifier);
         self.onlifeend && self.onlifeend(life);
      });
   };

   this.init();
}

上面这个TouchLifeTracer类是可以优化的,这里就留给广大的读者朋友了。代码中已经夹杂了很多注释,这里就不再累赘讲解分析了。

有了这个追踪器,我们使用的时候,就非常的方便了。比如:

// 获取元素
var a1 = document.getElementById('a1');

// 初始化
var tracer = new TouchLifeTracer({target:a1});
tracer.onlifeend = function (life) {
   //todo with life
};

最后,一个完整的示例,请参看https://github.com/lizhiqianduan/ycc/blob/develop/test/touch-event.html

结语

多点触控的追踪,只要理解了Touch,TargetTouches等属性,自己就可以撸一个手势库,比如Hammer.js,比如AlloyFinger。这些库都是基于多点触控来对手势进行模拟,比如tap、longTap、swipe等等。

这篇文章只是抛砖引玉,希望读者朋友能脑洞大开。



打赏作者

发表评论

电子邮件地址不会被公开。 必填项已用*标注