57 lines
1.6 KiB
JavaScript
57 lines
1.6 KiB
JavaScript
/**
|
|
* Vanilla scroll-scrubbed video (no GSAP).
|
|
* Pins via CSS sticky; maps scroll progress through spacer to video.currentTime.
|
|
*/
|
|
export function initScrollScrub() {
|
|
const root = document.querySelector("[data-scroll-scrub]");
|
|
if (!root) return;
|
|
|
|
const video = root.querySelector("video");
|
|
const spacer = root.querySelector(".scrub-spacer");
|
|
if (!(video instanceof HTMLVideoElement) || !spacer) return;
|
|
|
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
video.removeAttribute("autoplay");
|
|
video.controls = true;
|
|
spacer.style.display = "none";
|
|
return;
|
|
}
|
|
|
|
let ready = false;
|
|
let seeking = false;
|
|
let target = 0;
|
|
|
|
const onReady = () => {
|
|
ready = true;
|
|
video.pause();
|
|
};
|
|
|
|
if (video.readyState >= 2) onReady();
|
|
else video.addEventListener("loadeddata", onReady, { once: true });
|
|
|
|
video.addEventListener("seeked", () => {
|
|
seeking = false;
|
|
if (Math.abs(video.currentTime - target) > 0.04) {
|
|
seeking = true;
|
|
video.currentTime = target;
|
|
}
|
|
});
|
|
|
|
function update() {
|
|
if (!ready || !video.duration) return;
|
|
const rect = spacer.getBoundingClientRect();
|
|
const total = spacer.offsetHeight - window.innerHeight;
|
|
const scrolled = -rect.top;
|
|
const progress = total > 0 ? Math.min(1, Math.max(0, scrolled / total)) : 0;
|
|
target = progress * video.duration;
|
|
if (!seeking && Math.abs(video.currentTime - target) > 0.04) {
|
|
seeking = true;
|
|
video.currentTime = target;
|
|
}
|
|
}
|
|
|
|
window.addEventListener("scroll", update, { passive: true });
|
|
window.addEventListener("resize", update);
|
|
update();
|
|
}
|