问题描述:
在Ipad上使用pixijs时,发现当使用Apple Pencil时,笔触事件会被触发两次。具体表现为,当使用Pencil点击某一元素时,其点击事件会被触发两次。
问题原因:
分析pixijs
代码发现,其事件处理存在问题。
pixijs
的事件处理(EventSystem.ts)代码结构如下:
kotlin
public readonly supportsTouchEvents = 'ontouchstart' in globalThis;
public readonly supportsPointerEvents = !!globalThis.PointerEvent;
// 注册Pointer指针事件
if (this.supportsPointerEvents) {
this.domElement.addEventListener('pointerdown', this.onPointerDown, true);
this.domElement.addEventListener('pointerleave', this.onPointerOverOut, true);
this.domElement.addEventListener('pointerover', this.onPointerOverOut, true);
} else {
this.domElement.addEventListener('mousedown', this.onPointerDown, true);
this.domElement.addEventListener('mouseout', this.onPointerOverOut, true);
this.domElement.addEventListener('mouseover', this.onPointerOverOut, true);
}
// 注册Touche事件
if (this.supportsTouchEvents) {
this.domElement.addEventListener('touchstart', this.onPointerDown, true);
this.domElement.addEventListener('touchend', this.onPointerUp, true);
this.domElement.addEventListener('touchmove', this.onPointerMove, true);
}
private onPointerDown(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void {
// 如果支持触摸事件,则过滤掉pointerType为'touch'的指针事件,只响应TouchEvent类型的事件
if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return;
// ...
}
private onPointerUp(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void {
if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return;
// ...
}
private onPointerMove(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void {
if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return;
// ...
}
如上面代码所示。由于Pad端同时支持Pointer指针事件和Touch指针事件。因此,事件(onPointerDown
、onPointerUp
和onPointerMove
)会分别被this.domElement.addEventListener('pointerXxx', this.onPointerXxx, true)
和this.domElement.addEventListener('touchXxx', this.onPointerXxx, true)
注册两次。
而为了避免事件响应两次,pixijs
使用if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return;
做了一次过滤,确保在浏览器支持Touch事件时,过滤掉pointerType
为touch
的指针事件,只响应TouchEvent
类型的事件。而问题在于,当使用Apple Pencil时,pointerType
的值为pen
。此时,事件无法被过滤,PointerEvent
和TouchEvent
都会触发后续的事件响应。
解决方案:
针对pixijs
的事件过滤原则,增加pointerType
为pen
时的过滤条件。使用['touch', 'pen'].includes((nativeEvent as PointerEvent).pointerType)
替换(nativeEvent as PointerEvent).pointerType === 'touch'
同时过滤触摸和笔触事件。
详细修改方式参见:Fix: both touch and point events are called when using Apple penci 相关修复代码的PR已被官方合并,可通过升级pixijs的方式修复该问题。
如果不想升级,则可通过继承的方式重新注册事件,并采用强制重写相关私有函数的方式增加过滤:
kotlin
// 重写了父类私有方法,添加@ts-ignore
// @ts-ignore
export class CustomManager extends InteractionManager {
// 适配Apple Pencil,避免笔触事件触发两次
// 重写onPointerUp、onPointerMove和onPointerDown事件
// 由于这三个事件为私有事件,需要添加@ts-ignore忽略ts检查避免报错
onPointerUp(originalEvent: InteractivePointerEvent): void {
if (
this.supportsTouchEvents &&
['touch', 'pen'].includes((originalEvent as PointerEvent).pointerType)
) {
return;
}
// @ts-ignore
super.onPointerUp(originalEvent);
}
onPointerMove(originalEvent: InteractivePointerEvent): void {
if (
this.supportsTouchEvents &&
['touch', 'pen'].includes((originalEvent as PointerEvent).pointerType)
) {
return;
}
// @ts-ignore
super.onPointerMove(originalEvent);
}
onPointerDown(originalEvent: InteractivePointerEvent): void {
if (
this.supportsTouchEvents &&
['touch', 'pen'].includes((originalEvent as PointerEvent).pointerType)
) {
return;
}
// @ts-ignore
super.onPointerDown(originalEvent);
}
}