pixijs 适配 Apple Pencil

问题描述:

在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指针事件。因此,事件(onPointerDownonPointerUponPointerMove)会分别被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事件时,过滤掉pointerTypetouch的指针事件,只响应TouchEvent类型的事件。而问题在于,当使用Apple Pencil时,pointerType 的值为pen 。此时,事件无法被过滤,PointerEventTouchEvent 都会触发后续的事件响应。

解决方案:

针对pixijs的事件过滤原则,增加pointerTypepen 时的过滤条件。使用['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);
  }

}
相关推荐
gaolei_eit1 小时前
Vue3项目ES6转ES5,兼容低版本的硬件设备,React也
javascript·react.js·es6
一位搞嵌入式的 genius1 小时前
从 ES6 到 ESNext:JavaScript 现代语法全解析(含编译工具与实战)
前端·javascript·ecmascript·es6
linweidong3 小时前
C++ 模块化编程(Modules)在大规模系统中的实践难点?
linux·前端·c++
leobertlan6 小时前
2025年终总结
前端·后端·程序员
子兮曰7 小时前
OpenClaw架构揭秘:178k stars的个人AI助手如何用Gateway模式统一控制12+通讯频道
前端·javascript·github
百锦再8 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
莲华君8 小时前
React快速上手:从零到项目实战
前端·reactjs教程
百锦再8 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs
易安说AI8 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
颜酱9 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法