Skip to content

Commit d2fcc7f

Browse files
authored
High FPS hero video (#42)
2 parents 25475bf + 1b3e786 commit d2fcc7f

3 files changed

Lines changed: 59 additions & 2 deletions

File tree

-68 KB
Binary file not shown.
82.8 KB
Loading

website/src/pages/Home.tsx

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const HEADER_REVEAL_LEAD = 0.04;
3232
/** Fraction of runway where the hero text unpins and scrolls away (0–1).
3333
* The video keeps scrubbing underneath. */
3434
const UNPIN_THRESHOLD = 0.8;
35-
const HERO_VIDEO_FPS = 24;
35+
const HERO_VIDEO_FPS = 120;
3636

3737
/** Clamp a value to 0–1. */
3838
const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
@@ -297,9 +297,13 @@ function Home() {
297297
let frameId = 0;
298298
let handoffAnimationFrameId = 0;
299299
let handoffTimeoutId = 0;
300+
let initialRevealAnimationFrameId = 0;
301+
let initialRevealTimeoutId = 0;
302+
let initialVideoFrameCallbackId = 0;
300303
let videoFrameCallbackId = 0;
301304
let posterHandoffPending = false;
302305
let posterIsVisible = true;
306+
let videoCanReplacePoster = false;
303307
let disposed = false;
304308
let lastSeekFrame = -1;
305309

@@ -321,12 +325,50 @@ function Home() {
321325
handoffTimeoutId = 0;
322326
}
323327

328+
function cancelInitialVideoReveal() {
329+
if (initialVideoFrameCallbackId && videoWithFrameCallbacks.cancelVideoFrameCallback) {
330+
videoWithFrameCallbacks.cancelVideoFrameCallback(initialVideoFrameCallbackId);
331+
}
332+
initialVideoFrameCallbackId = 0;
333+
if (initialRevealAnimationFrameId) cancelAnimationFrame(initialRevealAnimationFrameId);
334+
initialRevealAnimationFrameId = 0;
335+
if (initialRevealTimeoutId) window.clearTimeout(initialRevealTimeoutId);
336+
initialRevealTimeoutId = 0;
337+
}
338+
324339
function completePosterHandoff() {
325340
if (disposed) return;
326341
cancelPosterHandoff();
327342
setPosterVisible(false);
328343
}
329344

345+
function revealInitialVideoFrame() {
346+
if (disposed || videoCanReplacePoster) return;
347+
cancelInitialVideoReveal();
348+
videoCanReplacePoster = true;
349+
scheduleScrollSync();
350+
}
351+
352+
function scheduleInitialVideoReveal() {
353+
if (videoCanReplacePoster || initialVideoFrameCallbackId || initialRevealAnimationFrameId) return;
354+
355+
if (videoWithFrameCallbacks.requestVideoFrameCallback) {
356+
initialVideoFrameCallbackId = videoWithFrameCallbacks.requestVideoFrameCallback(() => {
357+
initialVideoFrameCallbackId = 0;
358+
revealInitialVideoFrame();
359+
});
360+
initialRevealTimeoutId = window.setTimeout(revealInitialVideoFrame, 500);
361+
return;
362+
}
363+
364+
initialRevealAnimationFrameId = requestAnimationFrame(() => {
365+
initialRevealAnimationFrameId = requestAnimationFrame(() => {
366+
initialRevealAnimationFrameId = 0;
367+
revealInitialVideoFrame();
368+
});
369+
});
370+
}
371+
330372
function schedulePosterHandoff() {
331373
if (!posterIsVisible || posterHandoffPending) return;
332374
posterHandoffPending = true;
@@ -389,8 +431,11 @@ function Home() {
389431

390432
if (targetFrame === 0) {
391433
cancelPosterHandoff();
434+
if (!frameIsCurrent && !video.seeking) {
435+
video.currentTime = targetTime;
436+
}
392437
lastSeekFrame = 0;
393-
setPosterVisible(true);
438+
setPosterVisible(!(videoCanReplacePoster && frameIsCurrent && !video.seeking));
394439
} else if (targetFrame !== lastSeekFrame || (!video.seeking && !frameIsCurrent)) {
395440
const needsPosterHandoff = posterIsVisible;
396441
if (needsPosterHandoff) cancelPosterHandoff();
@@ -505,17 +550,26 @@ function Home() {
505550
window.addEventListener("touchstart", unlock, { passive: true });
506551
const handleCanPlayThrough = () => {
507552
unlocked = true;
553+
scheduleInitialVideoReveal();
508554
scheduleScrollSync();
509555
};
510556
const handleLoadedMetadata = () => {
511557
lastSeekFrame = -1;
558+
videoCanReplacePoster = false;
512559
cancelPosterHandoff();
560+
cancelInitialVideoReveal();
513561
setPosterVisible(true);
514562
scheduleScrollSync();
515563
};
564+
const handleLoadedData = () => {
565+
scheduleInitialVideoReveal();
566+
scheduleScrollSync();
567+
};
516568
video.addEventListener("canplaythrough", handleCanPlayThrough, { once: true });
569+
video.addEventListener("loadeddata", handleLoadedData);
517570
video.addEventListener("loadedmetadata", handleLoadedMetadata);
518571
video.addEventListener("durationchange", scheduleScrollSync);
572+
video.addEventListener("seeked", scheduleScrollSync);
519573

520574
function onScroll() {
521575
if (!unlocked) unlock();
@@ -529,12 +583,15 @@ function Home() {
529583
return () => {
530584
disposed = true;
531585
cancelPosterHandoff();
586+
cancelInitialVideoReveal();
532587
cancelAnimationFrame(frameId);
533588
window.removeEventListener("scroll", onScroll);
534589
window.removeEventListener("touchstart", unlock);
535590
video.removeEventListener("canplaythrough", handleCanPlayThrough);
591+
video.removeEventListener("loadeddata", handleLoadedData);
536592
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
537593
video.removeEventListener("durationchange", scheduleScrollSync);
594+
video.removeEventListener("seeked", scheduleScrollSync);
538595
};
539596
}, []);
540597

0 commit comments

Comments
 (0)