使用AnimationFrames做滚动动画时如何清除旧帧避免拖影?
问题原因
- 你遇到的拖影问题核心是每次绘制新帧前没有清空canvas画布上的旧内容,
cancelAnimationFrame主要是用来避免滚动事件高频触发产生的冗余动画帧请求,不是清帧的核心方案。 - 高频滚动时会触发多次
requestAnimationFrame,未执行的请求堆积也可能导致画面更新错乱,所以也需要配套做rAF的去重处理。
修复后的完整代码
const canvas = document.getElementById("hero-lightpass"); const context = canvas.getContext("2d"); const frameCount = 148; const currentFrame = index => ( `images/1 & 2 Intro & Exploded View_${index.toString().padStart(5, '0')}.png` ) // 存储requestAnimationFrame的id,用于取消冗余请求 let animationId = null; // 预加载图片缓存,避免反复修改img.src可能出现的加载延迟 const imgCache = []; const preloadImages = () => { for (let i = 1; i < frameCount; i++) { const img = new Image(); img.src = currentFrame(i); imgCache[i] = img; } }; 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 => { // 先清空画布上的旧帧内容,这是解决拖影的核心步骤 context.clearRect(0, 0, canvas.width, canvas.height); // 直接使用预加载的缓存图片,加载速度更快 const targetImg = imgCache[index] || img; context.drawImage(targetImg, 0, 0); } window.addEventListener('scroll', () => { const scrollTop = document.documentElement.scrollTop; const maxScrollTop = document.documentElement.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.ceil(scrollFraction * frameCount) ); // 先取消之前还未执行的动画帧请求,避免冗余执行 if (animationId) { cancelAnimationFrame(animationId); } animationId = requestAnimationFrame(() => updateImage(frameIndex + 1)) }); preloadImages()
关键修改说明
- 清画布逻辑:在
updateImage绘制新帧前调用context.clearRect(0, 0, canvas.width, canvas.height),直接清除上一帧的所有内容,从根源解决拖影问题。 - rAF去重逻辑:新增变量存储
requestAnimationFrame返回的ID,每次触发滚动事件时先调用cancelAnimationFrame取消上一次未执行的请求,避免高频滚动时的帧执行错乱。 - 优化图片加载:新增预加载图片缓存数组,绘制时直接取用缓存好的图片,避免反复修改
img.src可能带来的加载延迟和闪烁问题。 - 修复变量缺失问题:原代码中直接使用的
html变量未定义,替换为标准的document.documentElement获取滚动容器属性。
内容的提问来源于stack exchange,提问作者WoodyBRUH
相关产品推荐
相关产品推荐

