js PointerEvent指针事件简单介绍

6,576 阅读5分钟

指针事件

早期的浏览器,只存在鼠标事件(MouseEvent)。后来,以智能手机和平板电脑为首的触屏设备开始普及,交互方式发生了改变。但为了使现有功能不受影响,在很多情况下,触摸事件和鼠标事件会相继触发(以使非触摸专用的代码仍然可以与用户交互)。例如轻触屏幕会触发touchstart事件,如果不调用event.preventDefault()会继续触发mousedown事件。但在面对多点触控的时候,鼠标事件就显得无能为力了。因此,引入了触摸事件(TouchEvent)。不过这还不够完美,因为很多其他输入设备(如触控笔)有自己的特性。如果此时推出基于触控笔的API,那后面万一又有新特性的输入设备出现时,又怎么办呢?而且同时维护两份处理鼠标事件和触摸事件的代码已经很笨重了。面对这些问题,W3C急需一套能够整合输入事件的API,指针事件应运而生。指针事件(PointerEvent)是HTML5的事件规范之一,它主要目的是用来将鼠标(Mouse),触摸(Touch)和触控笔(Pen)三种事件整合为统一的API。

449809-20171219114446381-308449891.png

指针事件属性

指针事件属性继承自MouseEventEvent。常用属性例如: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表示一个指针是否是当前设备类型的主指针

指针事件类型

MouseEventTouchEventPointerEvent
mousedowntouchstartpointerdown
mousemovetouchmovepointermove
mouseuptouchendpointerup
touchcancelpointercancel
mouseenterpointerenter
mouseleavepointerleave
mouseoverpointerover
mouseoutpointerout
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事件的目标元素相同。

image.png

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);
            }
        }
    }
}

以上代码简单实现了多点触控(两个触摸点),如果需要两个以上触摸点的小伙伴需要自己实现哦。效果如下:

GIF 2021-07-08 13-14-22.gif

在线demo:jsdemo.codeman.top/html/pointe…

总结

指针事件允许我们通过一份代码,同时处理鼠标,触摸和触控笔事件。极大的方便了开发者。对指针事件感兴趣的小伙伴不要忘了上手实操一下。毕竟,”纸上得来终觉浅,绝知此事要躬行“。