Skip to content

Commit 00ecf93

Browse files
committed
await animation context
1 parent f4eff2a commit 00ecf93

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

render/src/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ async fn wait_for_frame_api(page: &Page) {
9090
page.evaluate(script).await.unwrap();
9191
}
9292

93+
async fn wait_for_animation_ready(page: &Page) {
94+
let script = r#"
95+
(async () => {
96+
const api = window.__frameScript;
97+
if (api && typeof api.waitAnimationsReady === "function") {
98+
await api.waitAnimationsReady();
99+
}
100+
})()
101+
"#;
102+
page.evaluate(script).await.unwrap();
103+
}
104+
93105
#[tokio::main]
94106
async fn main() -> Result<(), Box<dyn std::error::Error>> {
95107
let args = std::env::args().collect::<Vec<String>>();
@@ -245,6 +257,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
245257
let page = browser.new_page(page_url).await.unwrap();
246258
page.wait_for_navigation().await.unwrap();
247259
wait_for_frame_api(&page).await;
260+
wait_for_animation_ready(&page).await;
248261

249262
for frame in start..end {
250263
wait_for_next_frame(&page).await;

src/lib/animation.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,81 @@ type InternalContext = AnimationContext & {
5050
}
5151

5252
let nextOwnerId = 1
53+
const ANIMATION_TRACKER_KEY = "__frameScript_AnimationTracker"
54+
55+
type AnimationTracker = {
56+
pending: number
57+
start: () => () => void
58+
wait: () => Promise<void>
59+
}
60+
61+
const getAnimationTracker = (): AnimationTracker => {
62+
const g = globalThis as unknown as Record<string, unknown>
63+
const existing = g[ANIMATION_TRACKER_KEY] as AnimationTracker | undefined
64+
if (existing) return existing
65+
66+
let pending = 0
67+
const waiters = new Set<() => void>()
68+
69+
const notifyIfReady = () => {
70+
if (pending !== 0) return
71+
for (const resolve of Array.from(waiters)) {
72+
resolve()
73+
}
74+
waiters.clear()
75+
}
76+
77+
const tracker: AnimationTracker = {
78+
get pending() {
79+
return pending
80+
},
81+
start: () => {
82+
pending += 1
83+
let done = false
84+
return () => {
85+
if (done) return
86+
done = true
87+
pending = Math.max(0, pending - 1)
88+
notifyIfReady()
89+
}
90+
},
91+
wait: () => {
92+
if (pending === 0) {
93+
return Promise.resolve()
94+
}
95+
return new Promise<void>((resolve) => {
96+
waiters.add(resolve)
97+
})
98+
},
99+
}
100+
101+
g[ANIMATION_TRACKER_KEY] = tracker
102+
return tracker
103+
}
104+
105+
const installAnimationApi = () => {
106+
if (typeof window === "undefined") return
107+
const tracker = getAnimationTracker()
108+
const waitAnimationsReady = async () => {
109+
// Wait until pending is zero and stays zero through a tick (handles StrictMode double-effects).
110+
while (true) {
111+
if (tracker.pending === 0) {
112+
if (typeof window.requestAnimationFrame !== "function") {
113+
return
114+
}
115+
await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()))
116+
if (tracker.pending === 0) return
117+
}
118+
await tracker.wait()
119+
}
120+
}
121+
122+
;(window as any).__frameScript = {
123+
...(window as any).__frameScript,
124+
waitAnimationsReady,
125+
getAnimationsPending: () => tracker.pending,
126+
}
127+
}
53128

54129
const toFrames = (value: number) => Math.max(0, Math.round(value))
55130
const isDev = typeof import.meta !== "undefined" && Boolean((import.meta as any).env?.DEV)
@@ -198,6 +273,16 @@ export const useAnimation = (
198273
useProvideClipDuration(durationFrames)
199274

200275
useLayoutEffect(() => {
276+
installAnimationApi()
277+
const tracker = getAnimationTracker()
278+
const finishPending = tracker.start()
279+
let finished = false
280+
const finalize = () => {
281+
if (finished) return
282+
finished = true
283+
finishPending()
284+
}
285+
201286
if (!ownerIdRef.current) {
202287
ownerIdRef.current = nextOwnerId
203288
nextOwnerId += 1
@@ -272,11 +357,13 @@ export const useAnimation = (
272357
}
273358

274359
if (runIdRef.current !== runId) {
360+
finalize()
275361
return
276362
}
277363
const nextDuration = Math.max(1, Math.round(internal.maxFrame))
278364
setDurationFrames(nextDuration)
279365
setReady(true)
366+
finalize()
280367
}
281368

282369
void execute()
@@ -289,6 +376,7 @@ export const useAnimation = (
289376
variable._state.segments.length = 0
290377
}
291378
}
379+
finalize()
292380
}
293381
}, deps)
294382

0 commit comments

Comments
 (0)