一、移动端事件类型
- touchstart:手指按下事件,类似 mousedown
- touchmove:手指移动事件,类似 mousemove
- touchend 手指抬起事件,类似 mouseup
var box = document.querySelector('.container');
box.addEventListener('touchstart', () => {console.log('手指按下去了');
});
box.addEventListener('touchmove', () => {console.log('手指滑动了');
});
box.addEventListener('touchend', () => {console.log('手指抬起了');
});
二、事件对象
- changedTouches:触发当前事件的手指列表,也就是涉及当前(引发)事件的触摸点的列表
- targetTouches:位于当前 DOM 元素上的手指列表,也就是当前对象上所有触摸点的列表
- touches:位于当前屏幕上的所有手指列表(必需至少有 1 个手指在添加触发事件的元素上),也就是当前屏幕上所有触摸点的列表
box.addEventListener('touchstart', (e) => {console.log(e.changedTouches);console.log(e.targetTouches);console.log(e.touches);
});
三、移动端事件和 PC 端事件区别
虽说有了移动端专属的事件,并不意味着原本 PC 端的浏览器事件就不能用了,但是还是推荐尽量使用移动端的专属事件,因为 PC 端的事件并不是专门为移动端设计的,因此会存在各种各样的问题,
1、触发点区别
PC 端
-
mousemove:不需要鼠标按下,但是必需在元素上才能触发
-
mouseup:必需在元素上抬起才能触发
移动端
-
touchmove:必需手指按下才能触发,但是,按下后不在元素上也能触发
-
touchend:不需要在元素上抬起就能触发
2、触发顺序
触发顺序依次为:touchstart → touchend → mousedown → click → mouseup
3、touchstart 与 click 的区别
- touchstart 为手指碰到元素就触发
- click 为手指碰到元素并且抬起才会触发
四、移动端事件的bug
300ms延迟问题
为什么移动端点击事件要加 300ms 延迟呢?
早在 2007 年初,苹果公司在发布首款 iPhone 前夕,遇到一个问题:当时的网站都是为大屏幕设备所设计的。于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览桌面端站点的问题。
这当中最出名的,当属双击缩放( double tap to zoom ),这也是会有上述 300 毫秒延迟的主要原因。
双击缩放,顾名思义,即用手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器会将网页缩放至原始比例。 那么这和 300 毫秒延迟有什么联系呢?
假定这么一个场景: 用户在 iOS Safari 里边点击了一个链接。由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。
鉴于 iPhone 的成功,其他移动浏览器都复制了 iPhone Safari 浏览器的多数约定,包括双击缩放,几乎现在所有的移动端浏览器都有这个功能。
在 2014 年,从 Chrome32 开始 Google 已经解决这个 300ms 延迟问题,只要定义了 viewport 就不会有 300ms 延迟问题。
除此之外,我们来看一下移动端事件和 PC 端事件之间的其他区别。虽然上面介绍的 3 个移动端事件,都能找到其在 PC 端中相似的事件,但是仔细一比较,也是存在如下区别的:
事件穿透
所谓事件穿透,是移动端上面一个非常有名的 Bug,其出现场景为:有两层重叠的元素,上面的元素有 touch 事件(点击后要消失),下面是一个默认会触发 click 事件的元素(a、表单元素、带 click 事件的元素),此时点击上一层的元素,下一层也同样会被点击。
为什么会存在事件穿透呢?
这是因为在移动端浏览器,事件执行的顺序是 touchstart → touchmove → touched → click。而 click 事件有 300ms 的延迟,当 touchstart 事件把上层元素隐藏之后,隔了 300ms,浏览器触发了 click 事件,但是此时上层元素不见了,所以该事件被派发到了下层元素身上。
那么既然存在这个问题,该如何解决呢?
有一个最简单的解决方式,那就是取消事件的默认行为,如下:
box.addEventListener('touchstart', ev => {box.style.display = 'none';ev.preventDefault(); // 取消事件的默认动作
});
当我们阻止了 touchstart 事件的默认行为后,事件穿透也就随即消失。
五、移动端事件三方库
1、Swiper.js 轮播图库
官网地址:https://www.swiper.com.cn/
这是一个非常方便的制作轮播图的的第三方库,支持各种姿势的轮播,很多大厂也是在自己的移动端网页中使用这个库
2、Hammer.js 移动端手势库
官网地址:http://hammerjs.github.io/
优势
-
Hammer.js 不需要依赖任何其他的 JS 框架或者库,并且整个框架非常小,v2.0.4 版本只有 3.96kb。
-
Hammer.js 可以完美的实现在移端开发的大多数事件,如:点击、滑动、拖动、多点触控等事件。
示例
<div id="test" class="test"></div>
//创建一个新的hammer对象并且在初始化时指定要处理的dom元素
var hammertime = new Hammer(document.getElementById("test"));
//为该dom元素指定触屏移动事件
hammertime.on("pan", function (ev) {//控制台输出console.log(ev);
});
Hammer.js 主要事件如下:
1、Rotate 事件
在指定的 dom 区域内,当两个手指或更多手指成圆型旋转时触发(就像两个手指拧螺丝一样)。
- Rotate:所有旋转的集合
- Rotatestart:旋转开始
- Rotatemove:旋转过程
- Rotateend:旋转结束
- Rotatecancel:旋转取消
<div id="test" class="test">事件区域</div>
<div id="result" class="result">事件结果:旋转触发<br /></div>
html,
body {width: 100%;height: 100%;margin: 0px;padding: 0px;
}.test {width: 100%;height: 50%;background: #ffd800;text-align: left;
}.result {width: 100%;height: 50%;background: #b6ff00;text-align: left;
}
// 创建一个新的 hammer 对象并且在初始化时指定要处理的 dom 元素
var hammertime = new Hammer(document.getElementById("test"));
// 为该 dom 元素指定触屏旋转事件
hammertime.add(new Hammer.Rotate());
// 添加事件
hammertime.on("rotate", function (e) {document.getElementById("result").innerHTML += "X偏移量:【" + e.deltaX + "】,Y偏移量:【" + e.deltaY +"】<br />";//控制台输出console.log(e);
});
2、Pinch 事件
在指定的 dom 区域内,两个手指(默认为两个手指,多指触控需要单独设置)或多个手指相对(越来越近)移动或相向(越来越远)移动时事件。除了 Pinch 事件以外,该事件事以分别对以下事件进行监听并处理:
- Pinchstart:多点触控开始
- Pinchmove:多点触控过程
- Pinchend:多点触控结束
- Pinchcancel:多点触控取消
- Pinchin:多点触控时两手指距离越来越近
- Pinchout:多点触控时两手指距离越来越远
<div id="test" class="test">事件区域</div>
<div id="result" class="result">事件结果:捏合触发<br /></div>
// 创建一个新的 hammer 对象并且在初始化时指定要处理的dom元素
var hammertime = new Hammer(document.getElementById("test"));
// 为该 dom 元素指定触屏移动事件
hammertime.add(new Hammer.Pinch());
//添加事件
hammertime.on("pinchin", function (e) {document.getElementById("result").innerHTML += "捏合初触发<br />";//控制台输出console.log(e);
});
3、Press 事件
在指定的 dom 区域内触屏版本的点击事件,这个事件相当于 PC 端的 Click 事件,该不能包含任何的移动,最小按压时间为 500 毫秒,常用于我们在手机上用的“复制、粘贴”等功能。
- Press:按压后触发
- Pressup:按压离开时触发
<div id="test" class="test">事件区域</div>
<div id="result" class="result">事件结果:按压超过500ms触发<br /></div>
//创建一个新的hammer对象并且在初始化时指定要处理的dom元素
var hammertime = new Hammer(document.getElementById("test"));
//添加事件
hammertime.on("press", function (e) {document.getElementById("result").innerHTML += "超过500ms了<br />";//控制台输出console.log(e);
});
4、Pan 事件
在指定的 dom 区域内,一个手指放下并移动事件,即触屏中的拖动事件。这个事件在屏触开发中比较常用,如:左拖动、右拖动等,如手要上使用 QQ 时向右滑动出现功能菜单的效果。除了 Pan 事件以外,该事件还可以分别对以下事件进行监听并处理:
- Panstart:拖动开始
- Panmove:拖动过程
- Panend:拖动结束
- Pancancel:拖动取消
- Panleft:向左拖动
- Panright:向右拖动
- Panup:向上拖动
- Pandown:向下拖动
使用示例如下:
<div id="test" class="test">事件区域</div>
<div id="result" class="result">事件结果<br /></div>
//创建一个新的hammer对象并且在初始化时指定要处理的dom元素
var hammertime = new Hammer(document.getElementById("test"));
//添加事件
hammertime.on("pan", function (e) {document.getElementById("result").innerHTML += "X偏移量:【" + e.deltaX + "】,Y偏移量:【" + e.deltaY +"】<br />";//控制台输出console.log(e);
});
5、Tap 事件
在指定的 dom 区域内,一个手指轻拍或点击时触发该事件(类似 PC 端的 click)。该事件最大点击时间为 250 毫秒,如果超过 250 毫秒则按 Press 事件进行处理。
<div id="test" class="test">事件区域</div>
<div id="result" class="result">事件结果:点击触发<br /></div>
//创建一个新的hammer对象并且在初始化时指定要处理的dom元素
var hammertime = new Hammer(document.getElementById("test"));
//添加事件
hammertime.on("tap", function (e) {document.getElementById("result").innerHTML += "点击触发了,长按无效<br />";//控制台输出console.log(e);
});
6、Swipe 事件
在指定的 dom 区域内,一个手指快速的在触屏上滑动。即我们平时用到最多的滑动事件。
- Swipe:下面所有滑动的集合
- Swipeleft:向左滑动
- Swiperight:向右滑动
- Swipeup:向上滑动
- Swipedown:向下滑动
<div id="test" class="test">事件区域</div>
<div id="result" class="result">事件结果:向左滑动触发<br /></div>
//创建一个新的hammer对象并且在初始化时指定要处理的dom元素
var hammertime = new Hammer(document.getElementById("test"));
//添加事件
hammertime.on("swipeleft", function (e) {document.getElementById("result").innerHTML += "X偏移量:【" + e.deltaX + "】,Y偏移量:【" + e.deltaY +"】<br />";//控制台输出console.log(e);
});