为何window.requestAnimationFrame无法保留上一次过渡状态?
解决requestAnimationFrame实现滑块时transform3d重置的问题
问题现象
开发类似fullpage.js的滑块组件时,使用window.requestAnimationFrame实现页面过渡动画,每次切换幻灯片时,动画都会从第一页(translate3d(0,0,0))开始,而非从当前页面位置平滑过渡到目标页面。直接赋值transform属性时功能正常,但结合requestAnimationFrame就出现位置重置问题。
问题根源
当前代码的核心问题在于:
- 动画计算的是目标页面的绝对偏移量(比如第二页是
-100vh),用delta * offset直接从0开始过渡,忽略了滑块当前的实际位置 - 未处理动画过程中的滚轮事件拦截,可能导致多个动画叠加,进一步打乱位置状态
解决方案
通过记录当前实际偏移量、计算位置差值、增加动画锁三个核心修改,修复动画重置问题:
修改后的完整代码
HTML(保持不变)
<div class="scroller"> <div class="scroller-content"> <section class="scroller-section s1">SLIDE 1</section> <section class="scroller-section s2">SLIDE 2</section> <section class="scroller-section s3">SLIDE 3</section> <section class="scroller-section s4">SLIDE 4</section> </div> </div>
CSS(保持不变)
.scroller { width: 100vw; height: 100vh; overflow-y: hidden; } .scroller-section { width: 100vw; height: 100vh; display: flex; text-align: center; align-items: center; justify-content: center; font-size: 25rem; } .scroller-section.s1 { background-color: #535bf2; } .scroller-section.s2 { background-color: #242424; } .scroller-section.s3 { background-color: #213547; } .scroller-section.s4 { background-color: #747bff; }
JavaScript(核心修改)
const scrollerContent = document.querySelector(".scroller-content") const offsets = [0, -100, -200, -300] // 各页面的目标偏移值(单位vh) let currentIndex = 0 let currentOffset = 0 // 实时记录滑块当前的偏移量 let isAnimating = false // 动画锁,防止动画叠加 // 边界处理函数:确保索引不超出页面范围 function shift(newIndex) { if (newIndex < 0) return 0 if (newIndex >= offsets.length) return offsets.length - 1 return newIndex } window.addEventListener("wheel", (e) => { if (isAnimating) return // 动画未完成时不响应滚轮事件 e.preventDefault() // 阻止浏览器默认滚动行为 let targetIndex = currentIndex if (e.deltaY > 0) { targetIndex = shift(currentIndex + 1) } else if (e.deltaY < 0) { targetIndex = shift(currentIndex - 1) } if (targetIndex === currentIndex) return // 已经在目标页面,无需执行动画 const targetOffset = offsets[targetIndex] const offsetDiff = targetOffset - currentOffset // 计算当前位置到目标位置的差值 createAnimationFrames((delta) => { // 基于当前偏移量 + 差值*动画进度,计算实时位置 const currentAnimOffset = currentOffset + offsetDiff * delta scrollerContent.style.transform = `translate3d(0, ${currentAnimOffset}vh, 0)` }, 1000, () => { // 动画完成后更新状态,解锁动画 currentIndex = targetIndex currentOffset = targetOffset isAnimating = false }) isAnimating = true }) // 增强版requestAnimationFrame工具函数,增加完成回调 function createAnimationFrames(callback, duration, onComplete) { const start = performance.now() // 用performance.now()获取更高精度的时间戳 const raf = requestAnimationFrame const animate = () => { const now = performance.now() const delta = Math.min((now - start) / duration, 1) callback(delta) if (delta < 1) { raf(animate) } else { onComplete?.() } } raf(animate) }
关键修改说明
- currentOffset变量:实时记录滑块的当前偏移值,动画不再从0开始,而是基于这个值计算过渡路径
- offsetDiff差值计算:动画过程中是从当前位置向目标位置移动,而非直接跳到目标页面的绝对偏移
- isAnimating动画锁:防止滚轮事件在动画未完成时触发新的动画,避免位置冲突
- performance.now():替代
new Date()获取时间戳,精度更高,动画更流畅 - 动画完成回调:确保动画结束后才更新索引和当前偏移,保证状态一致性
内容的提问来源于stack exchange,提问作者Amia
相关产品推荐
相关产品推荐

