EventManager / createDoubleTapDetector
以 AbortController 为底座的作用域事件注册表,外加一个不区分指针类型的双击检测器。
它解决的是「装上去的监听怎么全部摘下来」。removeEventListener 必须拿到与注册时完全相同的函数引用和 options 才生效,中途包了一层箭头函数,就再也摘不掉了。反复挂载卸载的组件于是每个周期漏一个监听。AbortController 把这件事变成一次 abort()。
使用
在 Web Component 里
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 重置
}
}在普通页面代码里
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 |
必填 |
options |
addEventListener 的 options(不含 signal) |
Omit<AddEventListenerOptions,'signal'> |
- |
返回
| 参数 | 说明 | 类型 |
|---|---|---|
this |
manager 本身,便于链式 | EventManager |
delegate
事件委托:只在 parent 上挂一个监听,事件来自匹配 selector 的后代时才触发 handler。可链式调用。
handler 拿到原始事件和命中的元素两个参数。
scope.delegate(list, '.item', 'click', (ev, item) => {
console.log(item.getAttribute('data-id'));
});参数
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
parent |
挂载单个监听的元素 | HTMLElement |
必填 |
selector |
后代需要匹配的选择器 | string |
必填 |
type |
事件名 | string |
必填 |
handler |
(event, 命中元素) => void |
Function |
必填 |
options |
addEventListener 的 options(不含 signal) |
Omit<AddEventListenerOptions,'signal'> |
- |
返回
| 参数 | 说明 | 类型 |
|---|---|---|
this |
manager 本身,便于链式 | EventManager |
abort
摘掉全部已注册的监听,并重置内部 AbortController。可以重复调用;之后的 on() / delegate() 从一个干净的作用域重新开始。
返回
无返回值(void)
signal
底层的 AbortSignal,需要时可以自己透传给 addEventListener。
| 参数 | 说明 | 类型 |
|---|---|---|
signal |
该 manager 的 abort signal | AbortSignal |
createDoubleTapDetector
基于原始 (x, y, time) 采样点做双击检测,不区分指针类型,无论传入的是 Pointer、Touch 还是 Mouse 事件,效果都一样。为触摸手势场景而写(双击跳转、双击缩放、双击点赞),因为在每个调用点重新推导"时间间隔 + 距离阈值"的判断逻辑很容易出细微的错:只比较了一个轴,或者命中后忘记重置,导致三次快速点击被算成两次重叠的双击。
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) | number |
300 |
maxDistancePx |
两次点击之间的最大距离(px) | number |
60 |
DoubleTapDetector
| 成员 | 说明 | 类型 |
|---|---|---|
check |
记录一次 (x, y) 点击,并判断它是否和上一次点击构成双击。命中后会重置内部状态,因此第三次快速点击会开始一组新的判断,而不会被算作同一次双击的一部分。 |
(x: number, y: number, now?: number) => boolean |
reset |
清除上一次记录的点击,在发生点击以外的手势(比如拖拽)时调用 | () => void |