如何用原生JS消除canvas滚动重绘异常 实现AirPods同款360滚动展示效果
问题根因
- 预加载逻辑仅触发了图片下载,没有将加载完成的图片对象存入缓存数组,滚动切换帧时仍然需要重新等待图片加载,drawImage执行时图片未就绪导致画面空白、闪烁
- 滚动事件未做节流,短时间内会触发数十次帧更新请求,渲染逻辑执行混乱出现跳帧
- 帧切换逻辑直接修改img.src后立刻执行绘制,没有等待当前帧图片加载完成就渲染,必然出现绘制异常
修复后完整代码
JS代码
const html = document.documentElement; const canvas = document.getElementById("hero-lightpass"); const context = canvas.getContext("2d"); const frameCount = 148; // 图片缓存数组 const imageCache = []; let currentFrameIndex = 1; // 节流标记,避免重复触发渲染 let isUpdating = false; const currentFrame = index => (`https://connectplus.s3.ap-southeast-2.amazonaws.com/connect-plus/${index.toString().padStart(4, '0')}.png`) // 预加载逻辑修改:加载完成后存入缓存数组 const preloadImages = async () => { const loadImage = (index) => { return new Promise((resolve) => { const img = new Image(); img.src = currentFrame(index); img.onload = () => { imageCache[index] = img; resolve(); }; }); }; // 批量加载所有帧 const loadTasks = []; for(let i = 1; i <= frameCount; i++){ loadTasks.push(loadImage(i)); } await Promise.all(loadTasks); // 所有帧加载完成后再绑定滚动事件,避免加载过程中滚动出现异常 initScrollEvent(); }; // 初始化画布参数 canvas.width = 1158; canvas.height = 770; const renderFrame = (index) => { if(!imageCache[index] || isUpdating) return; isUpdating = true; // 清空画布后绘制新帧 context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(imageCache[index], 0, 0); currentFrameIndex = index; isUpdating = false; } const initScrollEvent = () => { window.addEventListener('scroll', () => { const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount, Math.max(1, Math.ceil(scrollFraction * frameCount)) ); // 只有帧索引变化时才触发重绘,避免无效渲染 if(frameIndex === currentFrameIndex) return; requestAnimationFrame(() => renderFrame(frameIndex)) }); } // 预加载完成后渲染第一帧 preloadImages().then(() => { renderFrame(1); });
CSS代码(可选优化,解决canvas画面模糊问题)
html { height: 100vh; } body { margin: 0; padding: 0; width: auto; height: auto; } .test { height: 500vh; background-color: #ffffff; } canvas { /* 关闭canvas平滑插值,避免帧画面模糊 */ image-rendering: -moz-crisp-edges; image-rendering: -webkit-crisp-edges; image-rendering: pixelated; image-rendering: crisp-edges; }
HTML代码无修改,保持原有逻辑即可
内容的提问来源于stack exchange,提问作者tengkuzulfadli
相关产品推荐
相关产品推荐

