对于PC端,我们的点击事件比较简单,因为鼠标指针只有一个。但是对于移动端来说,存在多个接触点同时操作的情况,这种情况就是我们今天要研究的主题–多点触控。
还没有关注公众号的朋友,请扫描二维码,或者识别图片二维码关注哦。
接触点类Touch
移动端的任何触摸都会触发一个事件,该事件会携带一个touchList列表来表示:当前有哪些接触点正在作用于我们的屏幕。
通俗点的解释,接触点可以理解为你的手指,touchList列表就代表了当前总共有几个手指正在与屏幕接触。
而touchList列表中的每一项都是Touch类的对象,它包含了我们手指与屏幕接触的必要信息:接触点的位置、压力大小、接触面积等等。
所以,在介绍之前,我们必须要先了解这个类。它代表的是屏幕上接触的点。具体属性值如下:
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对象
读者朋友可以看出来,上面这个类,准确的定义了一个接触点的各种属性。那么,在哪才能得到这些接触点的对象呢?
聪明如你,估计已经猜到了,那就是在触摸事件中。
注意属性中的identifier哦~非常重要的!
触摸事件
上面也提到了,Touch类只会存在于触摸事件里。常用的触摸事件有touchestart、touchmove、touchend、touchcacel。
它们都是TouchEvent类的实例,TouchEvent类的属性包含了前面介绍的接触点Touch实例列表。我们主要关注它的三个属性即可,如下:
TouchEvent.changedTouches 这是一个只读属性,是一个TouchList对象,它包含了代表所有从上一次触摸事件到此次事件过程中,状态发生了改变的触点的 Touch 对象。
TouchEvent.touches 这是一个只读属性,是一个TouchList对象,它包含了所有当前正在接触的接触点Touch对象,无论它们的起始于哪个element上,也无论它们状态是否发生了变化。
TouchEvent.targetTouches 这是一个只读属性,是一个TouchList对象,它包含了如下触点的 Touch 对象:触摸起始于当前事件的目标 element 上,并且仍然没有离开触摸平面的触点。
完整的属性列表,可以参考MDN文档,https://developer.mozilla.org/zh-CN/docs/Web/API/TouchEvent。
对于手势库来说,这几个TouchList属性已经够用了。
上面这段介绍,我们可以看出,在触摸的整个过程中,我们都可以找到接触点的一个引用,以此,我们可以结合Touch对象的ID来对每个触摸进行追踪。
接触点的生命周期类
想一想我们一个手指在屏幕上滑动的过程:开始接触->接触时的移动->接触结束。
这实际上就是一个接触点的生命周期,我们来为这一过程定义一个数据结构吧。
/** * 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 = []; }; })();
生命周期的开始和结束都只需要一个Touch对象,而移动可以多次移动,需要一个TouchList来保存。
另外附加了一个生命周期的ID,用来唯一标识一次生命周期。
多点触控的追踪器
有了接触点的生命周期,我们还需要一个追踪器,用来追踪多个接触点的生命周期。
试想一下,对于PC,只有一个鼠标指针,我们能很好的记录它的运行轨迹。
但是对于移动端,当存在多个接触点同时作用于屏幕时,怎么来记录他们的运行轨迹呢?
这就是追踪器的主要功能。
除此之外,在某个生命周期状态变化的时候,追踪器应该能通知应用程序做处理。
这里直接给出它的实现:
/** * 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(); }
上面这个算是最简单的追踪器了,它仅仅包含了最基本的数据结构和方法。
几个必要的属性:追踪器作用的对象、存活的生命周期、全部生命周期。 几个必要的方法:新增生命周期、删除生命周期、查找生命周期、追踪器的初始化。 几个必要的事件:生命周期的开始、生命周期的变化、生命周期的结束。
存活的生命周期对应于我们正在接触屏幕的手指,全部生命周期对应于我们所有接触屏幕的记录。
有了这个追踪器,我们在监控多点触控的时候,就相当的方便了。比如:
// 获取元素 var a1 = document.getElementById('a1'); // 初始化 var tracer = new TouchLifeTracer({target:a1}); tracer.onlifestart = function (life) { //todo with life console.log('检测到一个新的接触点'); }; tracer.onlifeend = function (life) { //todo with life console.log('检测到一个接触点离开'); };
抛砖引玉
上篇文章介绍了移动端手势的判断条件(http://www.lizhiqianduan.com/blog/index.php/2019/08/26/condition-of-guesture/),这篇文章我们介绍了多点触控的追踪器。
我们把他们结合一下,是不是就可以自己撸一个手势库出来了呢?答案是肯定的。
百度搜手势库比较靠前的,比如Hammer.js,比如AlloyFinger,这些库都是基于这两点基础知识来对手势进行模拟的。
始终相信,别人能做到的,我们也能做到。
那么,下篇文章我们就结合两篇文章,真正的实现一个手势库,敬请期待!
最后
不要忘了关注官方公众号哦~发布的优质文章能第一时间通知您哦~只需要扫文章顶部的二维码就行了,感谢您的支持!
公众号郑重承诺:只推送原创干货
打赏作者