移动端时代已经到来,做为前端开发的咱们没有理由也不该该坐井观天,而是勇敢地跳出内心的那口井,去拥抱蔚蓝的天空。该来的总会来,咱们要作的就是接受未知的挑战。正如你所看到的,这是一篇关于移动端触摸事件的文章,也就是咱们平时在手机中用得最多的动做:touch。如今让咱们开始 touch touch touch 吧!html
首先 touch 包含三类事件,它们分别是:touchstart、touchmove、touchend 。望文生义这种本能相信你应该会有,但在这里我仍是有必需对这三个词进行一翻没必要要的解释。前端
授课时间
touchstart:手指触摸到一个 DOM 元素时触发。浏览器
touchmove:手指在一个 DOM 元素上滑动时触发。ide
touchend:手指从一个 DOM 元素上移开时触发。测试
这三个事件又分别对应三个相同的触摸列表:scala
授课时间
touches:正在触摸屏幕的全部手指的一个列表。code
targetTouches:正在触摸当前 DOM 元素上的手指的一个列表。htm
changedTouches:涉及当前事件的手指的一个列表。对象
事件对应的三个列表虽然名字不同,可是它们里面装的东西都是差很少的,包含了当前事件的一些相关信息,好比:一些坐标信息。事件
TouchList {0: Touch, length: 1} length:1 0:Touch clientX:65 // 触摸点在浏览器窗口中的横坐标 clientY:18 // 触摸点在浏览器窗口中的纵坐标 force:1 // 触摸点压力大小 identifier:0 // 触摸点惟一标识(ID) pageX:65 // 触摸点在页面中的横坐标 pageY:18 // 触摸点在页面中的纵坐标 radiusX:11.5 // 触摸点椭圆的水平半径 radiusY:11.5 // 触摸点椭圆的垂直半径 rotationAngle:0 // 旋转角度 screenX:560 // 触摸点在屏幕中的横坐标 screenY:175 // 触摸点在屏幕中的纵坐标 target:div#touchLog 触摸目标 __proto__:Touch __proto__:TouchList
上面就是一个 TouchList 列表。它对应的就是前面提到的三种事件(touchstart、touchmove、touchend)中的一种,在触发时生成的一个对象列表。列表里最有用的就是 Touch 对象了,Touch 对象里存放着对应事件的一些相关的信息,咱们就是经过这种个事件里这些属性的有机结合来实现各类效果。
经过上面的 radiusX,radiusY,rotationAngle 这三个属性就能够计算出你的手指触摸手机屏幕时的一个接触面,只不过这个接触面是用一个近似的椭圆来表示,也就是说它不是一个真正意义上的接触面,而是一个大概的接触面。相信心细的朋友应该会看到 TouchList 对象里有一个 length 属性,而且它的值为 1 ,这说明当前只有一个手指触发了事件(好比:touchstart 事件),换句话说,此时你只有一个手指放到了手机屏幕上,这个手指对应的一些信息存放在 Touch 对象里。由于只有一个手指放在了屏幕上,因此这个 TouchList 里只有一个 Touch 对象,而且是第一个下标为 0 。TouchList 列表里还有一个 target 属性,这个应该很好理解,就是触摸的目标。
为了让你能更加立体地理解上面的这些属性,我专门从网上找了一段话来做为补充:
来自 mozilla
1.Touch.identifier:此 Touch 对象的惟一标识符。 一次触摸动做(咱们指的是手指的触摸)在平面上移动的整个过程当中,该标识符不变。 能够根据它来判断跟踪的是不是同一次触摸过程,此值为只读属性。
2.Touch.screenX:触点相对于屏幕左边沿的X坐标。只读属性。
3.Touch.screenY:触点相对于屏幕上边沿的Y坐标。只读属性。
4.Touch.clientX:触点相对于可见视区(visual viewport)左边沿的X坐标。不包括任何滚动偏移。只读属性。
5.Touch.clientY:触点相对于可见视区(visual viewport)上边沿的Y坐标。不包括任何滚动偏移。只读属性。
6.Touch.pageX:触点相对于HTML文档左边沿的X坐标。当存在水平滚动的偏移时,这个值包含了水平滚动的偏移。只读属性。
7.Touch.pageY:触点相对于HTML文档上边沿的Y坐标。当存在水平滚动的偏移时,这个值包含了垂直滚动的偏移。只读属性。
8.Touch.radiusX:可以包围用户和触摸平面的接触面的最小椭圆的水平轴(X轴)半径。这个值的单位和 screenX 相同。只读属性。
9.Touch.radiusY:可以包围用户和触摸平面的接触面的最小椭圆的垂直轴(Y轴)半径。这个值的单位和 screenY 相同。只读属性。
10.Touch.rotationAngle:它是这样一个角度值:由radiusX 和 radiusY描述的正方向的椭圆,须要经过顺时针旋转这个角度值,才能最精确地覆盖住用户和触摸平面的接触面。只读属性。
11.Touch.force:手指挤压触摸平面的压力大小,从0.0(没有压力)到1.0(最大压力)的浮点数。只读属性。
12.Touch.target:当这个触点最开始被跟踪时(在 touchstart 事件中),触点位于的HTML元素。哪怕在触点移动过程当中,触点的位置已经离开了这个元素的有效交互区域,或者这个元素已经被从文档中移除。须要注意的是,若是这个元素在触摸过程当中被移除,这个事件仍然会指向它,可是不会再冒泡这个事件到 window 或 document 对象。所以,若是有元素在触摸过程当中可能被移除,最佳实践是将触摸事件的监听器绑定到这个元素自己,防止元素被移除后,没法再从它的上一级元素上侦测到从该元素冒泡的事件。只读属性。
为了更深刻地理解 Touch 事件,咱们如今来作一个简单的 DEMO 。
HTML 代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>移动端触摸(touch)事件</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <style> #touchLog { padding: 12px; width: 100%; box-sizing: border-box; height: 300px; } </style> </head> <body> <div id="touchLog">这里显示 touch 信息</div> </body> </html>
JavaScript 代码
<script> var obj = document.getElementById("touchLog"); obj.addEventListener("touchstart", showMsg); function showMsg(ev) { console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } </script>
上面绑定的是 touchstart 事件,其它两个事件的用法同样,只是触发的时间点不同而已。运行上面的代码会输出以下图的结果:
可能你会问:为何它们的数据都是同样的,其实也很好理解,当你按下屏幕触发 touchstart 此时,而后执行了showMsg() 方法。那改为 touchmove 呢?不出意外的话,当你移动手指时第一组中的这三个 TouchList 也是同样的,为何说第一组呢,由于 touchmove 是在你移动手指时才会触发的,因此当你不断移动手指时会屡次触发天然而然地就会出现多组数据。但对于 touchend 就有点不同了。你能够运行以下代码查看 touchend 与其它两个的区别:
<script> var touchLog = document.getElementById("touchLog"); touchLog.addEventListener("touchstart", showMsgTouchstart); function showMsgTouchstart(ev) { console.log("---touchstart---"); console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } touchLog.addEventListener("touchmove", showMsgTouchmove); function showMsgTouchmove(ev) { console.log("---touchmove---"); console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } touchLog.addEventListener("touchend", showMsgTouchend); function showMsgTouchend(ev) { console.log("---touchend---"); console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } </script>
运行后你会发现 touchend 的 touches、targetTouches 里是没有 touch 对象的,length 值都为零,以下图:
这里咱们还能够作别一个更有意思的测试,那就是触摸目标元素后,手指再滑出目标元素。上面三个事件对应的 touches、targetTouches 和 changedTouches 又会输出什么呢?还会是同样?
若是不出意外
1.touchstart 事件:touches、targetTouches 和 changedTouches 是同样的。
2.touchmove 事件:touches、targetTouches 和 changedTouches 是同样的(数据依然会有多组,缘由上面也已经分析过了)。
3.touchend 事件:当你手指离开屏幕后,也就是 touchend 事件触发时,touches、targetTouches 的 TouchList 的 length 一样为0,也就是说没有 touch 对象。
因此使用 touchend 事件时须要注意只有 changedTouches 会存有触摸对象。但在 TouchList 对象中的 target 属性的值都为 div#touchLog,也就是说无论你触摸屏幕后手指是在目标元素上仍是滑出目标元素,这个 target 属性的值仍是 div#touchLog 。只要触摸了屏幕 force 的值都是 1 ,因此在这里感受这个 force 尚未什么用武之地。
咱们先来看看下面的这一段代码,而且若是条件容许的话请用手机访问:http://yunkus.com/demo/mobile-touch-event/multi-finger-touchstart.html 查看touchstart 事件触发时的效果,代码以下:
<script> var obj = document.getElementById("touchLog"); obj.addEventListener("touchstart", showMsg); function showMsg(ev) { obj.innerHTML = ""; var touchesStr = ""; var targetTouchesStr = ""; for (var i = 0; i < ev.touches.length; i++) { touchesStr += "identifier:" + ev.touches[i].identifier + ",x 轴坐标:" + ev.touches[i].clientX + "<br>"; targetTouchesStr += "identifier:" + ev.targetTouches[i].identifier + ",x 轴坐标:" + ev.targetTouches[i] .clientX + "<br>"; } obj.innerHTML = "下面是 ev.touches 数据:<br>" + touchesStr + "<br>下面是 ev.targetTouches 数据:<br>" + targetTouchesStr + "<br>下面是 ev.changedTouches 数据:<br>" + "identifier:" + ev.changedTouches[0].identifier + ",x 轴坐标:" + ev.changedTouches[0].clientX + "<br>"; } </script>
访问 http://yunkus.com/demo/mobile-touch-event/multi-finger-touchmove.html 查看 touchmove 事件触发时的效果,touchmove 事件触发后 changedTouches 里的 touch 对象就不仅一个了,而是跟其它两个 TouchList 列表同样。
访问 http://yunkus.com/demo/mobile-touch-event/multi-finger-touchend.html 查看 touchend 事件触发时的效果,touchmove 事件触发后就只有 changedTouches 里有 touch 且只有一个 touch 对象了,无论你同时在屏幕上放了多少根手指,这个 touch 对象对应的是你最后一次离开屏幕的那根手指。
上面的三个 demo 都会输出事件触发时的 touches 、targetTouches 和 changedTouches 里的 identifier 值,以及一个clientX 值。clientX 用于让你经过 x 的坐标来判断哪根手指对应哪一个 identifier 的。对于 touchstart 事件而言,由于 changedTouches 里老是保存一个 touch 对象,因此没有遍历,而是直接经过下标访问。从上面咱们能够得知有 touchstart 事件中 touches 、targetTouches 和 changedTouches 是有区别的:changedTouches 下只有一个 touch 对象,这个对象对应着触发事件最后一根发生改变(好比:最后触摸屏幕)的手指。这也就是为何 changedTouches 里只有一个 touch 对象的缘由,由于某一时刻下老是只有一个手指在变化。
可是也不能以偏盖全,由于 touchmove 事件中的 changedTouches 里就不仅一个,而是跟 touches 和 targetTouches 一样有多个 touch 对象。
正如前面所说的 touchend 只有 changedTouches 列表里只有一个 touch 对象,这个对象对应着最后一根手指发生的改变(好比:最后离开屏幕)的手指。
经过研究单指操做跟多指操做,就可让咱们对 touch 事件了解得更加立体,到位。
在移动端手指操做时会默认触发一些行为,好比:滚动,缩放。上面的例子是没有阻止触摸事件的默认行为的。因此当你测试上面 multi-finger-touchmove.html 这个例子时,你会发现有时候你会感到很无助,页面很容易发生缩放行为,甚至影响到测试效果。要想阻止触摸事件的默认行为也很是地简单只须要添加以下代码就能够了:
document.addEventListener("touchstart", function (ev) { ev.preventDefault(); });
添加触摸事件的阻止默认行为的好处也不只仅只有这一个。
1.在IOS 10 下设置 meta 禁止用户缩放是没有效果的,使用ev.preventDefault(); 就能够实现禁止用户缩放页面。
2.解决 IOS 10 下溢出隐藏(不起做用)的问题。
3.禁止系统默认的滚动条(如:横向滚动条)、以及橡皮筋效果。
4.禁止长按选中文字、选中图片、系统默认菜单。
5.解决点透问题。
虽然有那么多好处,须要注意的是此时也会带来一些问题,好比:input 不能获取焦点了。不过你能够经过单独的给 input 标签添加 touchstart 事件,而且阻止其冒泡就可让 input 标签重生了。
var inputObj = document.getElementsByTagName("input")[0]; document.addEventListener("touchstart", function (ev) { ev.preventDefault(); }); inputObj.addEventListener("touchstart", function (ev) { ev.stopPropagation(); });
这里有一个 Demo,经过 touch 的相关事件实现的一个移动端焦点图切换效果 :http://yunkus.com/demo/mobile-touch-event/。