Skip to content

EventManager / createDoubleTapDetector

AbortController 为底座的作用域事件注册表,外加一个不区分指针类型的双击检测器。

它解决的是「装上去的监听怎么全部摘下来」。removeEventListener 必须拿到与注册时完全相同的函数引用和 options 才生效 —— 中途包了一层箭头函数,就再也摘不掉了。反复挂载卸载的组件于是每个周期漏一个监听。AbortController 把这件事变成一次 abort()

使用

在 Web Component 里

ts
import { EventManager } from 'ranuts/utils';

class MyElement extends HTMLElement {
  private _events = new EventManager();

  connectedCallback() {
    this._events.on(this._input, 'input', this.handleInput).on(this, 'click', this.handleClick, { capture: true });
  }

  disconnectedCallback() {
    this._events.abort(); // 摘掉全部监听,并为下一次 connect 重置
  }
}

在普通页面代码里

ts
function initSection(container: HTMLElement) {
  const scope = new EventManager();

  scope.on(input, 'input', handleSearch).delegate(container, '[data-action]', 'click', (ev, target) => {
    handleAction(target.getAttribute('data-action'));
  });

  return () => scope.abort(); // 区块销毁时调用
}

API

on

注册一个归属于该 manager 的监听。可链式调用。

参数

参数说明类型默认值
target事件目标EventTarget必填
type事件名string必填
handler处理函数EventListener必填
optionsaddEventListener 的 options(不含 signalOmit<AddEventListenerOptions,'signal'>-

返回

参数说明类型
thismanager 本身,便于链式EventManager

delegate

事件委托:只在 parent 上挂一个监听,事件来自匹配 selector 的后代时才触发 handler。可链式调用。

handler 拿到原始事件和命中的元素两个参数。

ts
scope.delegate(list, '.item', 'click', (ev, item) => {
  console.log(item.getAttribute('data-id'));
});

参数

参数说明类型默认值
parent挂载单个监听的元素HTMLElement必填
selector后代需要匹配的选择器string必填
type事件名string必填
handler(event, 命中元素) => voidFunction必填
optionsaddEventListener 的 options(不含 signalOmit<AddEventListenerOptions,'signal'>-

返回

参数说明类型
thismanager 本身,便于链式EventManager

abort

摘掉全部已注册的监听,并重置内部 AbortController。可以重复调用;之后的 on() / delegate() 从一个干净的作用域重新开始。

返回

无返回值(void

signal

底层的 AbortSignal,需要时可以自己透传给 addEventListener

参数说明类型
signal该 manager 的 abort signalAbortSignal

createDoubleTapDetector

基于原始 (x, y, time) 采样点做双击检测——不区分指针类型,无论是 Pointer、Touch 还是 Mouse 事件喂进来效果都一样。为触摸手势场景而写(双击跳转、双击缩放、双击点赞),因为在每个调用点重新推导"时间间隔 + 距离阈值"的判断逻辑很容易出细微的错:只比较了一个轴,或者命中后忘记重置,导致三次快速点击被算成两次重叠的双击。

ts
import { createDoubleTapDetector } from 'ranuts/utils';

const detector = createDoubleTapDetector();
el.addEventListener('pointerup', (e) => {
  if (detector.check(e.clientX, e.clientY)) seek();
});

createDoubleTapDetector(options?)

参数(DoubleTapDetectorOptions

参数说明类型默认值
windowMs两次点击之间的最大间隔(ms)number300
maxDistancePx两次点击之间的最大距离(px)number60

DoubleTapDetector

成员说明类型
check记录一次 (x, y) 点击,并判断它是否和上一次点击构成双击。命中后会重置内部状态,因此第三次快速点击会开始一组新的判断,而不会被算作同一次双击的一部分。(x: number, y: number, now?: number) => boolean
reset遗忘上一次记录的点击——当发生点击以外的手势(比如拖拽)时调用() => void

Released under the MIT License.