Skip to content

Commit 97aa5e0

Browse files
committed
refactor: timer factory
1 parent b72e6d7 commit 97aa5e0

File tree

1 file changed

+85
-23
lines changed

1 file changed

+85
-23
lines changed
Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,104 @@
1-
export interface RafIntervalOptions {
1+
export interface TimerBaseContext {
22
startMs: number
33
deltaMs: number
44
}
55

6-
export function setRafInterval(callback: (options: RafIntervalOptions) => void, interval: number) {
7-
let start = performance.now()
8-
let handle: number
6+
interface TimerContext extends TimerBaseContext {
7+
now: number
8+
}
9+
10+
export type TimerContextFn = (ctx: TimerContext) => boolean | void
11+
12+
const currentTime = () => performance.now()
13+
14+
export class Timer {
15+
private frameId: number | null = null
16+
private pausedAtMs: number | null = null
17+
private context: TimerContext
18+
19+
constructor(private readonly onTick: TimerContextFn) {
20+
this.context = { now: 0, startMs: currentTime(), deltaMs: 0 }
21+
}
22+
23+
private cancelFrame = () => {
24+
if (this.frameId === null) return
25+
cancelAnimationFrame(this.frameId)
26+
this.frameId = null
27+
}
28+
29+
setStartMs = (startMs: number) => {
30+
this.context.startMs = startMs
31+
}
32+
33+
get elapsedMs(): number {
34+
if (this.pausedAtMs !== null) {
35+
return this.pausedAtMs - this.context.startMs
36+
}
37+
return currentTime() - this.context.startMs
38+
}
939

10-
function loop(now: number) {
11-
const delta = now - start
40+
start = () => {
41+
if (this.frameId !== null) return
1242

13-
if (delta >= interval) {
14-
start = interval > 0 ? now - (delta % interval) : now
15-
callback({ startMs: start, deltaMs: delta })
43+
const now = currentTime()
44+
if (this.pausedAtMs !== null) {
45+
this.context.startMs += now - this.pausedAtMs
46+
this.pausedAtMs = null
47+
} else {
48+
this.context.startMs = now
1649
}
1750

18-
handle = requestAnimationFrame(loop)
51+
this.frameId = requestAnimationFrame(this.#tick)
1952
}
2053

21-
handle = requestAnimationFrame(loop)
22-
return () => cancelAnimationFrame(handle)
23-
}
54+
pause = () => {
55+
if (this.frameId === null) return
56+
this.cancelFrame()
57+
this.pausedAtMs = currentTime()
58+
}
2459

25-
export function setRafTimeout(callback: () => void, delay: number) {
26-
const start = performance.now()
27-
let handle: number
60+
stop = () => {
61+
if (this.frameId === null) return
62+
this.cancelFrame()
63+
this.pausedAtMs = null
64+
}
65+
66+
#tick = (now: number) => {
67+
this.context.now = now
68+
this.context.deltaMs = now - this.context.startMs
2869

29-
function loop(now: number) {
30-
const delta = now - start
70+
const shouldContinue = this.onTick(this.context)
3171

32-
if (delta >= delay) {
33-
callback()
72+
if (shouldContinue === false) {
73+
this.stop()
3474
return
3575
}
3676

37-
handle = requestAnimationFrame(loop)
77+
this.frameId = requestAnimationFrame(this.#tick)
3878
}
79+
}
80+
81+
export function setRafInterval(fn: (ctx: TimerBaseContext) => void, intervalMs: number) {
82+
const timer = new Timer(({ now, deltaMs }) => {
83+
if (deltaMs >= intervalMs) {
84+
const startMs = intervalMs > 0 ? now - (deltaMs % intervalMs) : now
85+
timer.setStartMs(startMs)
86+
fn({ startMs, deltaMs })
87+
}
88+
})
89+
90+
timer.start()
91+
return () => timer.stop()
92+
}
93+
94+
export function setRafTimeout(fn: () => void, delayMs: number) {
95+
const timer = new Timer(({ deltaMs }) => {
96+
if (deltaMs >= delayMs) {
97+
fn()
98+
return false
99+
}
100+
})
39101

40-
handle = requestAnimationFrame(loop)
41-
return () => cancelAnimationFrame(handle)
102+
timer.start()
103+
return () => timer.stop()
42104
}

0 commit comments

Comments
 (0)