为什么处理函数在移动端触发两次,以及拖动之后的 click
关于 Canvas 上的指针输入,有两件事常让人意外。一件是你必须处理的真实问题;另一件已经被 处理好了,知道这一点可以让你省掉一个并不需要的变通方案。
一次点按可能触发三个事件
Konva 会监听全部三个 DOM 输入事件族——鼠标、触摸和指针——这样处理函数无需你事先选择就能 在各处工作。但它不会把它们合并。
在触摸屏上,浏览器会发送一组 touchstart/touchend、一组 pointerdown/pointerup,
然后为了兼容触摸出现之前编写的页面,再合成一组 mousedown/mouseup。Konva 会把它们
各自转换成自己的事件。一次点按会产生:
| Konva 事件 | 来源 |
|---|---|
tap | 触摸事件对 |
pointerclick | 指针事件对 |
click | 合成的鼠标事件对 |
因此下面这段代码在手机上会让处 理函数执行两次,在桌面端执行一次:
// Fires for `click` and again for `tap` on a touch device.
shape.on('click tap', handleSelect);
只绑定一个事件族
最简单的办法是不再成对绑定事件,改用指针事件族——它已经覆盖了鼠标、触摸和触控笔:
shape.on('pointerclick', handleSelect); // once, on every device
pointerdown、pointermove 和 pointerup 的用法相同。指针事件默认开启
(Konva.pointerEventsEnabled),更多内容参见
指针事件。
如果你需要支持不带指针事件的浏览器,或者正在维护已经成对绑定 click tap 的代码,
那就为处理函数加一道防护:
let lastHandled = 0;
function handleSelect(e) {
// A synthesised mouse event follows its touch within about 300ms.
const now = Date.now();
if (now - lastHandled < 400) return;
lastHandled = now;
// ...
}
请优先只绑定一个事件族。计时器只是变通方案,它会吞掉手速快的用户真正的第二次点按。
拖动之后 click 本来就不会触发
这一点值得了解,因为常见的变通方案其实没有必要。
当拖动真正开始时,Konva 会清除允许派发 click 的标志,因此 dragend 之后不会
再触发 click:
shape.on('dragend', () => console.log('dragged'));
shape.on('click', () => console.log('clicked'));
// Drag the shape: you get 'dragged' only.
// Press and release without moving: you get 'clicked' only.
如果你仍然看到拖动之后出现 click,通常是下面几种原因之一。
你监听的是 mouseup 而不是 click。 mouseup 是原始指针事件,无论是否拖动都会触发。
Konva 抑制的是 click。
指针的移动距离不足以算作拖动。 Konva.dragDistance 默认为 3 像素。按下时偏移
一两个像素——这在触摸屏上很正常——会被当作一次点击,dragstart 也不会触发。这是预期行为。
如果你的用户需要在很小的目标上操作且手不够稳,可以调高它:
Konva.dragDistance = 8;
你把 dragDistance 设成了 0。 那么每次按下都是拖动,于是你想要的 click 被抑制了。
阻止浏览器自身的手势
与 Konva 无关的是,在 Canvas 上触摸可能会滚动页面或触发双击缩放。那是浏览器的行为, 需要用 CSS 阻止:
#container {
touch-action: none;
}
如果页面仍应可以垂直滚动、同时由 Canvas 处理水平拖动,请改用 touch-action: pan-y。
完整说明参见移动端滚动。