指针事件
早期的浏览器,只存在鼠标事件(MouseEvent)。后来,以智能手机和平板电脑为首的触屏设备开始普及,交互方式发生了改变。但为了使现有功能不受影响,在很多情况下,触摸事件和鼠标事件会相继触发(以使非触摸专用的代码仍然可以与用户交互)。例如轻触屏幕会触发touchstart事件,如果不调用event.preventDefault()会继续触发mousedown事件。但在面对多点触控的时候,鼠标事件就显得无能为力了。因此,引入了触摸事件(TouchEvent)。不过这还不够完美,因为很多其他输入设备(如触控笔)有自己的特性。如果此时推出基于触控笔的API,那后面万一又有新特性的输入设备出现时,又怎么办呢?而且同时维护两份处理鼠标事件和触摸事件的代码已经很笨重了。面对这些问题,W3C急需一套能够整合输入事件的API,指针事件应运而生。指针事件(PointerEvent)是HTML5的事件规范之一,它主要目的是用来将鼠标(Mouse),触摸(Touch)和触控笔(Pen)三种事件整合为统一的API。
指针事件属性
指针事件属性继承自MouseEvent和Event。常用属性例如:clientX,clientY等都有。下面简单介绍指针事件独有的属性。
属性 | 介绍 |
---|---|
pointerId | 触发事件的指针的唯一提示 |
width | 指针的接触面的CSS像素宽度 |
height | 指针的接触面的CSS像素高度 |
pressure | 归一化后的指针压力值,范围在0-1之间 |
tangentialPressure | 归一化后的切向压力值,范围在-1-1]之间,0表示控制设备中立状态时的值 |
tiltX | 由输入设备(如手写笔)与Y轴的构成平面,和YZ平面之间的夹角,范围在-90-90之间 |
tiltY | 由输入设备(如手写笔)与X轴构成平面,和XZ平面之间的夹角,范围在-90-90之间 |
twist | 输入设备(如手写笔)围绕自身价值范围旋转的角度,范围在0-359之间 |
pointerType | 表示触发事件的设备类型,mouse,pen,touch |
isPrimary | 表示一个指针是否是当前设备类型的主指针 |
指针事件类型
MouseEvent | TouchEvent | PointerEvent |
---|---|---|
mousedown | touchstart | pointerdown |
mousemove | touchmove | pointermove |
mouseup | touchend | pointerup |
touchcancel | pointercancel | |
mouseenter | pointerenter | |
mouseleave | pointerleave | |
mouseover | pointerover | |
mouseout | pointerout | |
gotpointercapture | ||
lostpointercapture |
使用方式
MouseEvent
我们一般对于MouseEvent事件会这样处理:
<div id="box"></div>
const box = document.getElementById('box');
let isMouseDown = false;
// 将mousedown事件绑定到box元素上
box.addEventListener('mousedown', function (e) {
isMouseDown = true;
});
// 将mousemove事件绑定到document或window上,防止移动过快丢失目标元素
document.addEventListener('mousemove', function (e) {
if (isMouseDown) {
// todo
}
});
// 将mouseup事件绑定到document或window上,防止在目标元素外释放鼠标
document.addEventListener('mouseup', function (e) {
isMouseDown = false;
});
上述处理方式的问题在于,鼠标在文档周围的移动可能会引起副作用,触发其他元素的事件处理程序。
TouchEvent
<div id="box"></div>
const box = document.getElementById('box');
let point = { x: 0, y: 0 };
let point2 = { x: 0, y: 0 };
box.addEventListener('touchstart', function (e) {
point = { x: e.touches[0].clientX, y: e.touches[0].clientY };
// 第二个触摸点
if (pointers.length > 1) {
point2 = { x: e.touches[1].clientX, y: e.touches[1].clientY };
}
});
box.addEventListener('touchmove', function (e) {
const current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
// 第二个触摸点
if (e.touches.length > 1) {
const current2 = { x: e.touches[1].clientX, y: e.touches[1].clientY };
}
});
box.addEventListener('touchend', function (e) { });
box.addEventListener('touchcancel', function (e) { });
相对于鼠标事件需要将mousemove,mouseup绑定到document上,防止丢失目标元素的问题,在触摸事件中则不会发生。因为touchmove,touchend,touchcancel事件的目标和触发touchstart事件的目标元素相同。
PointerEvent
对于MouseEvent存在的问题,指针事件有对应的解决方法,就是Element.setPointerCapture()。我们可以在pointerdown事件处理程序中调用box.setPointerCapture(e.pointerId),这样接下来所发生的事件(例如pointerenter,pointerleave,pointerout,pointerover,pointerup,pointercancel)都会被重定向到box上。具体使用如下:
<div id="box"></div>
const box = document.getElementById('box');
let isPointerDown = false;
box.addEventListener('pointerdown', function (e) {
isPointerDown = true;
});
box.addEventListener('pointermove', function (e) {
box.setPointerCapture(e.pointerId);
if (isPointerDown) {
// todo
}
});
box.addEventListener('pointerup', function (e) {
isPointerDown = false;
});
box.addEventListener('pointercancel', function (e) {
isPointerDown = false;
});
如果在移动端使用,请给box元素添加touch-action: none;
多点触控
我们使用TouchEvent实现多点触控的时候,event会返回touches属性,该属性会列出所有当前在与触摸表面接触的Touch对象,不管触摸点是否已经改变或其目标元素是在处于touchstart阶段。但指针事件并没有类似的属性,而是需要我们自己处理。具体实现如下:
<div id="box"></div>
const box = document.getElementById('box');
let pointers = [];
let point = { x: 0, y: 0 };
let point2 = { x: 0, y: 0 };
box.addEventListener('pointerdown', function (e) {
// 维护一个数组,用于记录当前触摸点
pointers.push(e);
point = { x: pointers[0].clientX, y: pointers[0].clientY };
// 第二个触摸点
if (pointers.length > 1) {
point2 = { x: pointers[1].clientX, y: pointers[1].clientY };
}
});
box.addEventListener('pointermove', function (e) {
handlePointers(e, 'update');
const current = { x: pointers[0].clientX, y: pointers[0].clientY };
if (pointers.length > 1) {
const current2 = { x: pointers[1].clientX, y: pointers[1].clientY };
}
});
box.addEventListener('pointerup', function (e) {
handlePointers(e, 'delete');
});
box.addEventListener('pointercancel', function (e) {
pointers = [];
});
/**
* 处理指针
* @param {PointerEvent} e
* @param {string} type
*/
function handlePointers(e, type) {
for (let i = 0; i < pointers.length; i++) {
if (pointers[i].pointerId === e.pointerId) {
if (type === 'update') {
pointers[i] = e;
} else if (type === 'delete') {
pointers.splice(i, 1);
}
}
}
}
以上代码简单实现了多点触控(两个触摸点),如果需要两个以上触摸点的小伙伴需要自己实现哦。效果如下:
在线demo:jsdemo.codeman.top/html/pointe…
总结
指针事件允许我们通过一份代码,同时处理鼠标,触摸和触控笔事件。极大的方便了开发者。对指针事件感兴趣的小伙伴不要忘了上手实操一下。毕竟,”纸上得来终觉浅,绝知此事要躬行“。