如何实现序列图播放完成前禁止页面滚动(仿Apple效果)
仿Apple风格Canvas滚动序列图:加载完成前禁止页面滚动的实现修改
我想要制作一个仿Apple风格的页面,通过Canvas在滚动时展示图片序列,但在序列的最后一张图加载完成前,页面不随滚动移动。以下是我目前的实现代码,该如何修改?
现有代码
HTML
<body> <canvas id="image-hero"></canvas> <h1 class="centered title" style="font-size: 4rem;">Test Text</h1> </body>
JavaScript
const html = document.documentElement; const canvas = document.getElementById('image-hero'); const context = canvas.getContext('2d'); 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` ) const preloadImages = () => { for (let i = 1; i < frameCount; i++) { const img = new Image(); img.src = currentFrame(i); } }; const img = new Image() img.src = currentFrame(1); canvas.width = 1158; canvas.height = 770; img.onload = function() { context.drawImage(img, 0, 0); } const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0); } window.addEventListener('scroll', () => { //don't scroll the page until the animation is done const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.ceil(scrollFraction * frameCount) ); requestAnimationFrame(() => updateImage(frameIndex + 1)) }); preloadImages() disableScrolling();
修改方案
现有代码存在几个问题:未定义disableScrolling函数、没有跟踪图片加载进度、滚动事件未做加载前的拦截处理。以下是完整的修改实现:
1. 实现滚动禁用/启用逻辑
添加函数控制页面滚动权限,覆盖鼠标滚轮、触摸滑动、键盘按键等多种滚动触发方式:
// 禁用滚动 function disableScroll() { const preventDefault = (e) => e.preventDefault(); document.addEventListener('wheel', preventDefault, { passive: false }); document.addEventListener('touchmove', preventDefault, { passive: false }); // 阻止方向键、空格等键盘滚动操作 document.addEventListener('keydown', (e) => { const scrollKeys = [33, 34, 35, 36, 37, 38, 39, 40, 32]; if (scrollKeys.includes(e.keyCode)) e.preventDefault(); }); // 隐藏滚动条(可选) html.style.overflow = 'hidden'; } // 启用滚动 function enableScroll() { const preventDefault = (e) => e.preventDefault(); document.removeEventListener('wheel', preventDefault); document.removeEventListener('touchmove', preventDefault); document.removeEventListener('keydown', (e) => { const scrollKeys = [33, 34, 35, 36, 37, 38, 39, 40, 32]; if (scrollKeys.includes(e.keyCode)) e.preventDefault(); }); html.style.overflow = ''; }
2. 跟踪图片加载进度
修改preloadImages函数,通过计数器统计已加载图片数量,全部加载完成后自动启用滚动:
let loadedCount = 0; const preloadImages = () => { // 遍历所有帧图片(注意修正循环范围为1到frameCount) for (let i = 1; i <= frameCount; i++) { const img = new Image(); img.src = currentFrame(i); // 加载成功时更新计数器 img.onload = () => { loadedCount++; if (loadedCount === frameCount) { enableScroll(); } }; // 处理加载失败的情况(可选) img.onerror = () => { loadedCount++; console.warn(`图片 ${i} 加载失败`); if (loadedCount === frameCount) { enableScroll(); } }; } };
3. 调整滚动事件逻辑
在滚动事件中添加判断,仅当所有图片加载完成后才执行图片更新:
window.addEventListener('scroll', () => { // 未加载完成时直接返回,不处理滚动 if (loadedCount < frameCount) return; const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; // 用Math.floor替代Math.ceil,避免滚动初期就跳过多帧 const frameIndex = Math.min( frameCount - 1, Math.floor(scrollFraction * frameCount) ); requestAnimationFrame(() => updateImage(frameIndex + 1)); });
4. 初始化调整
确保页面加载时先禁用滚动,再启动图片预加载:
// 初始化时禁用滚动 disableScroll(); // 开始预加载图片 preloadImages();
完整修改后的代码
HTML
<body> <canvas id="image-hero"></canvas> <h1 class="centered title" style="font-size: 4rem;">Test Text</h1> </body>
JavaScript
const html = document.documentElement; const canvas = document.getElementById('image-hero'); const context = canvas.getContext('2d'); 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` ) let loadedCount = 0; // 禁用滚动 function disableScroll() { const preventDefault = (e) => e.preventDefault(); document.addEventListener('wheel', preventDefault, { passive: false }); document.addEventListener('touchmove', preventDefault, { passive: false }); document.addEventListener('keydown', (e) => { const scrollKeys = [33, 34, 35, 36, 37, 38, 39, 40, 32]; if (scrollKeys.includes(e.keyCode)) e.preventDefault(); }); html.style.overflow = 'hidden'; } // 启用滚动 function enableScroll() { const preventDefault = (e) => e.preventDefault(); document.removeEventListener('wheel', preventDefault); document.removeEventListener('touchmove', preventDefault); document.removeEventListener('keydown', (e) => { const scrollKeys = [33, 34, 35, 36, 37, 38, 39, 40, 32]; if (scrollKeys.includes(e.keyCode)) e.preventDefault(); }); html.style.overflow = ''; } const preloadImages = () => { for (let i = 1; i <= frameCount; i++) { const img = new Image(); img.src = currentFrame(i); img.onload = () => { loadedCount++; if (loadedCount === frameCount) { enableScroll(); } }; img.onerror = () => { loadedCount++; console.warn(`图片 ${i} 加载失败`); if (loadedCount === frameCount) { enableScroll(); } }; } }; const img = new Image() img.src = currentFrame(1); canvas.width = 1158; canvas.height = 770; img.onload = function() { context.drawImage(img, 0, 0); } const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0); } window.addEventListener('scroll', () => { if (loadedCount < frameCount) return; const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.floor(scrollFraction * frameCount) ); requestAnimationFrame(() => updateImage(frameIndex + 1)); }); // 初始化 disableScroll(); preloadImages();
内容的提问来源于stack exchange,提问作者Marvil
相关产品推荐
相关产品推荐

