如何修改JS监听滚动高度,基于Canvas父级Div实现苹果风格序列滚动?
Fixing the Canvas Scroll Animation to Trigger Only When Target Section is in Viewport
Great question! Let's break down why your change broke things and fix it step by step.
Why Your Original Change Failed
When you replaced document.documentElement with document.getElementById("wrap1"), the code stopped working because:
#wrap1isn't a scrollable container (it lacksoverflow: scroll/auto), so itsscrollTopvalue stays at 0 permanently.- The original logic relied on the entire document's scroll range, but you want the animation to activate only when the target section is visible in the viewport, using that section's context instead of the whole page.
Solution: Adjust Logic to Target the Section
We need to modify the code to:
- Detect when
#wrap1enters or exits the viewport - Calculate scroll progress relative to that section
- Only update the canvas frame when the section is visible
Modified Full Code
HTML (No Changes Needed)
<div class="wrap1" id="wrap1"> <div class="wrap" id="wrap"> <canvas id="hero-lightpass"></canvas> </div> </div>
CSS (Optional Adjustment)
Tweak the #wrap height to control how quickly the animation plays as you scroll:
#wrap1 { position: relative; z-index: 1; margin: 0; padding: 0; background: #000; } #wrap { height: 100vh; /* Adjust this to change animation scroll duration */ } canvas { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); max-width: 100vw; max-height: 100vh; }
JavaScript (Core Fixes)
const canvas = document.getElementById("hero-lightpass"); const context = canvas.getContext("2d"); const wrap1 = document.getElementById("wrap1"); // Target our section container const frameCount = 148; const currentFrame = index => (`https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`); // Preload all frames for smooth animation const preloadImages = () => { for (let i = 1; i < frameCount; i++) { const img = new Image(); img.src = currentFrame(i); } }; // Initialize first frame const img = new Image(); img.src = currentFrame(1); canvas.width = 1158; canvas.height = 770; img.onload = function() { context.drawImage(img, 0, 0); }; // Update canvas with the correct frame const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0); }; // Listen for window scroll window.addEventListener('scroll', () => { const rect = wrap1.getBoundingClientRect(); // Total scroll distance where the section is visible (from entry to exit) const totalScrollRange = wrap1.offsetHeight + window.innerHeight; // How far we've scrolled into the section's visible range const scrollOffset = window.scrollY - (wrap1.offsetTop - window.innerHeight); // Only run animation if the section is in the viewport if (rect.top < window.innerHeight && rect.bottom > 0) { // Clamp scroll fraction between 0 and 1 to avoid invalid frame indices const scrollFraction = Math.min(1, Math.max(0, scrollOffset / totalScrollRange)); // Map scroll fraction to frame number const frameIndex = Math.min(frameCount - 1, Math.ceil(scrollFraction * frameCount)); requestAnimationFrame(() => updateImage(frameIndex + 1)); } else { // Optional: Reset to first frame when section is out of view // requestAnimationFrame(() => updateImage(1)); } }); preloadImages();
Key Changes Explained
- Target the section: We use
wrap1instead of the entire document to reference our animation container. - Viewport detection:
getBoundingClientRect()tracks the section's position relative to the viewport, ensuring we only run the animation when it's visible. - Relative scroll calculation: We calculate progress based on the section's visible scroll range, not the entire page. This means the animation starts when the section enters the viewport and finishes when it exits.
- Value clamping:
Math.min()andMath.max()prevent invalid frame indices by keeping the scroll fraction between 0 and 1.
内容的提问来源于stack exchange,提问作者Marcus
相关产品推荐
相关产品推荐

