如何基于时长、起止位置计算JavaScript滚动的acceleration值?
问题
我用background-image和background-position-x实现滚动功能,点击Roll按钮触发roll()函数。功能里有起始位置startPosition和结束位置endPosition,roll.style.backgroundPosition的X值由currentPosition变量控制。
想要实现先快后慢的滚动效果,直到currentPosition等于endPosition,每次滚动时长固定在3-4秒左右。
但调整acceleration值时遇到问题:
- 调大
acceleration,currentPosition可能变成负数并无限循环; - 调小
acceleration,如果速度没降下来,会因为currentPosition提前到达endPosition而提前停止滚动。
而且每次执行时startPosition和endPosition是随机的,请问怎么基于duration、startPosition和endPosition精确计算acceleration的取值?
附带当前代码:
function roll() { let startPosition = 1000; let endPosition = 5000; let currentPosition = startPosition; let initialSpeed = 10; let duration = 4000; let startTime = 0; const acceleration = 0.1; const roll = document.querySelector('.rotation') function updatePosition() { startTime += 5 initialSpeed -= acceleration initialSpeed = Math.max(initialSpeed, 1); currentPosition += initialSpeed; if (currentPosition > endPosition) { currentPosition = endPosition; } roll.style.backgroundPosition = `${currentPosition}px center`; if (currentPosition < endPosition) { requestAnimationFrame(updatePosition); } } updatePosition(); }
解决方案
你的核心问题是用线性减速的方式控制滚动,但未结合总距离和总时长计算参数,导致要么提前停止要么循环。推荐用缓动函数实现先快后慢的效果,既无需手动计算加速度,还能精准控制时长,适配任意随机的起始/结束位置。
思路说明
- 放弃手动维护
initialSpeed和acceleration,改用时间比例计算当前位置:- 计算滚动总距离:
totalDistance = endPosition - startPosition - 记录滚动开始的时间戳,每次更新时计算已流逝时间占总时长的比例
t(范围0~1)
- 计算滚动总距离:
- 用缓动函数(比如
easeOutQuad)把线性时间比例转换成非线性位置比例,实现先快后慢的效果 - 根据位置比例计算当前
currentPosition,直接赋值给backgroundPosition
修改后的代码
function roll() { const startPosition = 1000; const endPosition = 5000; const duration = 4000; // 固定4秒,可改成3000~4000的随机值 const roll = document.querySelector('.rotation'); const startTime = performance.now(); const totalDistance = endPosition - startPosition; // 缓动函数:easeOutQuad,实现先快后慢的效果 function easeOutQuad(t) { return t * (2 - t); } function updatePosition() { const elapsed = performance.now() - startTime; // 计算时间比例,超过1就取1(防止超时) const progress = Math.min(elapsed / duration, 1); // 用缓动函数计算位置比例,再算出当前位置 const currentPosition = startPosition + totalDistance * easeOutQuad(progress); roll.style.backgroundPosition = `${currentPosition}px center`; if (progress < 1) { requestAnimationFrame(updatePosition); } else { // 确保最终位置精准落在endPosition roll.style.backgroundPosition = `${endPosition}px center`; } } updatePosition(); }
关键优势
- 完全不需要计算加速度,适配任意
startPosition和endPosition的随机组合 - 滚动时长严格控制在
duration设定的时间内,不会提前结束或超时 - 缓动函数可以替换成其他类型(比如
easeOutCubic),调整减速曲线,满足不同需求 - 不会出现
currentPosition负数或无限循环的问题
如果坚持用加速度计算(不推荐)
如果一定要用线性减速的方式,需要基于总距离和时长推导公式:
假设初始速度为v0,加速度为a(减速时a为正,速度逐渐减小),总时长为T,总距离为S。
线性减速到速度为0的公式:S = v0*T - 0.5*a*T²v0 = a*T(因为最终速度为0,v0 - a*T = 0)
代入得:S = 0.5*a*T² → a = 2*S / T²
但要注意:
- 时间单位要统一(比如T用秒,S用像素,a的单位是像素/秒²)
- 如果用
requestAnimationFrame,需要把时间转换成帧间隔(通常约16ms)来计算每帧的速度变化 - 这种方式不如缓动函数灵活,且容易出现边界问题(比如总距离太小导致初始速度过大)
内容的提问来源于stack exchange,提问作者ChanhHuynh
相关产品推荐
相关产品推荐

