hotschpotsh/product-scroll-poc/script.js

138 lines
3.6 KiB
JavaScript

gsap.registerPlugin(ScrollTrigger);
const canvas = document.querySelector("#cup-canvas");
const context = canvas.getContext("2d");
const video = document.querySelector("#cup-video");
const status = document.querySelector("#buffer-status");
const frames = [];
let isBuffered = false;
// 1. Canvas Setup
function resizeCanvas() {
if (video.videoWidth) {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
} else {
canvas.width = 1000;
canvas.height = 1000;
}
}
// 2. Buffering Logic (Capturing frames into memory)
video.addEventListener("loadedmetadata", () => {
resizeCanvas();
startBuffering();
});
async function startBuffering() {
video.currentTime = 0;
video.playbackRate = 1; // Standard speed for better capture quality
await video.play();
function capture() {
if (video.paused || video.ended) {
finishBuffering();
return;
}
// Draw video frame to worker canvas and store as ImageBitmap
createImageBitmap(video).then(bitmap => {
frames.push(bitmap);
// Draw first frame immediately
if (frames.length === 1) renderFrame(0);
requestAnimationFrame(capture);
});
}
requestAnimationFrame(capture);
}
function finishBuffering() {
isBuffered = true;
status.style.opacity = "0";
video.pause();
initScrollAnimation();
}
function renderFrame(index) {
if (!frames[index]) return;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(frames[index], 0, 0, canvas.width, canvas.height);
}
// 3. Optimized Scroll Animation
function initScrollAnimation() {
const totalFrames = frames.length - 1;
const scrollObj = { frame: 0 };
// GSAP Timeline for the whole experience
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".scroll-container",
start: "top top",
end: "bottom bottom",
scrub: 0.5, // Slight lag-behind for smoothness
}
});
// Frame scrubbing
tl.to(scrollObj, {
frame: totalFrames,
ease: "none",
onUpdate: () => renderFrame(Math.floor(scrollObj.frame))
}, 0);
// Intro Fade
gsap.to(".intro-content", {
opacity: 0,
y: -50,
scrollTrigger: {
trigger: ".intro-section",
start: "top top",
end: "bottom center",
scrub: true
}
});
// Sub-animations for depth
tl.to(canvas, {
scale: 0.95,
rotateY: 5,
ease: "sine.inOut"
}, 0);
// Content text reveal
const sections = document.querySelectorAll(".content-section");
sections.forEach((section) => {
const text = section.querySelector(".text-block");
gsap.fromTo(text,
{ opacity: 0, y: 40 },
{
opacity: 1,
y: 0,
duration: 1,
scrollTrigger: {
trigger: section,
start: "top 70%",
end: "top 30%",
toggleActions: "play reverse play reverse",
}
}
);
});
// Exit
gsap.to(".product-stage", {
opacity: 0,
scrollTrigger: {
trigger: ".outro-section",
start: "top center",
end: "bottom bottom",
scrub: true
}
});
}