出现原因
数位板的输入在浏览器上是通过监听touch事件实现的。一般情况下,一次完整的手写输入将依次触发 touchstart - touchmove - touchend 三种事件。但在某些场景下,比如用户在数位板上绘制的时间过短,起始点和结束点的距离过短,浏览器只能响应到 touchstart - touchend 两种事件,导致中间过程的输入位置数据缺失,影响用户体验。
解决方案
使用 PointerEvent 中的 pointermove 事件补全这段时间内的鼠标位置数据。
原理
简单来说,PointerEvent是输入设备的硬件层抽象(比如鼠标,触摸笔,或触摸屏上的一个触摸点),描述了用于处理来自设备(包括鼠标、笔、触摸屏等)的硬件无关指针输入的事件和相关接口,里面记录了输入的具体坐标,以及对应的输入设备类型(pen / mouse / touch)等相关参数,详细说明可以参考 PointerEvent MDN文档地址。
通过这个事件,无论输入的方式是数位板、鼠标还是触摸屏,都能直接获取到交互点的坐标位置,它有一个优势在于即使现在已经为每种输入设备类型分别设置监听函数,这个事件依然能够被触发(touch事件除外,后续有说明),而且是只要指针位置发生了改变就会立刻触发此事件。利用这种特性,可以很容易恢复 touchstart - touchend 这段时间内缺失的事件。
简单验证
可以观察到,在 touchstart后,touchmove事件并没有立刻触发,但此时鼠标指针是有移动的,如果仅使用touch事件捕捉鼠标位置,那么从touchstart到第一个touchmove事件中间这 300ms 的位置数据将丢失。使用了pointermove事件后,可以明显观察到这段时间内的鼠标位置变化都被很好地收集起来,效果不错。验证demo源码在文末提供。
PointerEvent 兼容性
PointerEvent 属于比较新的 API,现在主流浏览器的新版本都支持。
pointermove、touchmove、mousemove的触发条件区别
(1)mousemove
经过验证,pointermove和mousemove将会同时触发,因此这里只需要二选一即可,功能是一致的
(2)touchmove
其实从刚刚验证的图里面可以看到,当touchmove响应后,pointermove事件被浏览器取消了。MDN上有提到过:
The pointermove event is fired when a pointer changes coordinates, and the pointer has not been canceled by a browser touch-action.
这里有个解决方案,参考 StackOverflow 上的一篇回答,可以通过给dom节点设置 touch-action:none
即可同时触发 pointermove 和 touchmove 事件。
验证Demo源码
写得比较简单,轻喷
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test" style="width: 100vw; height: 100vh;"></div>
<script>
var dom = document.querySelector('#test');
var isTouchDown = false;
var isMouseDown = false;
var isPointDown = false;
dom.addEventListener('touchstart', () => {isTouchDown = true; console.log('%ctouchstart', 'color:#ff0000')}, false)
dom.addEventListener('touchend', () => { isTouchDown = false; console.log('%ctouchend', 'color:#ff0000')}, false)
dom.addEventListener('touchmove', () => { isTouchDown && console.log('%ctouchmove', 'color:#00ff00')}, false)
dom.addEventListener('mousedown', () => {isMouseDown = true; console.log('%cmousedown', 'color:#0000ff')}, false)
dom.addEventListener('mouseup', () => { isMouseDown = false; console.log('%cmouseup', 'color:#0000ff')}, false)
dom.addEventListener('mousemove', () => { isMouseDown && console.log('mousemove')}, false)
dom.addEventListener('pointerdown', () => {isPointDown = true; console.log('%cpointerdown', 'color:#00ffff')}, false)
dom.addEventListener('pointerup', () => { isPointDown = false; console.log('%cpointerup', 'color:#00ffff')}, false)
dom.addEventListener('pointermove', (e) => { isPointDown && console.log('%cpointermove', 'color:#aaaa00')}, false)
dom.addEventListener('pointerleave', () => { isPointDown && console.log('%cpointerleave', 'color:#00ffff')}, false)
</script>
</body>
</html>