改变世界的是这样一群人,他们寻找梦想中的乐园,当他们找不到时,他们亲手创造了它

移动端触摸(touch)事件

移动端时代已经到来,作为前端开发的我们没有理由也不应该坐井观天,而是勇敢地跳出心里的那口井,去拥抱蔚蓝的天空。该来的总会来,我们要做的就是接受未知的挑战。正如你所看到的,这是一篇关于移动端触摸事件的文章,也就是我们平时在手机中用得最多的动作:touch。现在让我们开始 touch touch touch 吧!

Touch 事件

首先 touch 包含三类事件,它们分别是:touchstart、touchmove、touchend 。望文生义这种本能相信你应该会有,但在这里我还是有必需对这三个词进行一翻不必要的解释。

授课时间

touchstart:手指触摸到一个 DOM 元素时触发。

 

touchmove:手指在一个 DOM 元素上滑动时触发。

 

touchend:手指从一个 DOM 元素上移开时触发。

这三个事件又分别对应三个相同的触摸列表:

授课时间

touches:正在触摸屏幕的所有手指的一个列表。

 

targetTouches:正在触摸当前 DOM 元素上的手指的一个列表。

 

changedTouches:涉及当前事件的手指的一个列表。

Touch 属性

事件对应的三个列表虽然名字不一样,但是它们里面装的东西都是差不多的,包含了当前事件的一些相关信息,比如:一些坐标信息。

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 初探

单指操作

为了更深入地理解 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 事件,其它两个事件的用法一样,只是触发的时间点不一样而已。运行上面的代码会输出如下图的结果:

移动端触摸(touch)事件

可能你会问:为什么它们的数据都是一样的,其实也很好理解,当你按下屏幕触发 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 值都为零,如下图:

移动端触摸(touch)事件

这里我们还可以做别一个更有意思的测试,那就是触摸目标元素后,手指再滑出目标元素。上面三个事件对应的 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/

posted @ 2018-12-28 11:29  水狼一族  阅读(471)  评论(0编辑  收藏  举报
改变世界的是这样一群人,他们寻找梦想中的乐园,当他们找不到时,他们亲手创造了它