forked from koshikraj/ottopus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpointer.ts
More file actions
54 lines (48 loc) · 1.52 KB
/
Copy pathpointer.ts
File metadata and controls
54 lines (48 loc) · 1.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
type Listener = (x: number, y: number) => void
const listeners = new Set<Listener>()
let x = 0
let y = 0
let moved = false
let frame = 0
function onMove(e: PointerEvent): void {
x = e.clientX
y = e.clientY
moved = true
// Coalesce to one notification per frame. A pointermove can fire far more
// often than the screen refreshes, and every extra call here would be work
// thrown away before anything was painted.
if (frame) return
frame = requestAnimationFrame(() => {
frame = 0
for (const listener of listeners) listener(x, y)
})
}
/**
* One pointer listener for the whole page, however many things are watching.
*
* Several mascots can be on screen at once — the sign-in dialog over a landing
* page already makes two — and each attaching its own `pointermove` would mean
* duplicate work on the most frequent event the browser fires.
*/
export function watchPointer(listener: Listener): () => void {
if (listeners.size === 0) {
window.addEventListener('pointermove', onMove, { passive: true })
}
listeners.add(listener)
// A late subscriber should not sit at the origin until the next movement.
if (moved) listener(x, y)
return () => {
listeners.delete(listener)
if (listeners.size === 0) {
window.removeEventListener('pointermove', onMove)
if (frame) {
cancelAnimationFrame(frame)
frame = 0
}
}
}
}
/** True once the pointer has moved at all. False on a touch device at rest. */
export function pointerHasMoved(): boolean {
return moved
}