手写一个通用的 animate 动画工具函数:支持多属性插值、动画时长、缓动函数、每帧回调,可被手动中断,并返回 Promise(参考 PDD 前端笔试题)。
实现
function animate({ duration, easing = t => t, onUpdate, onAbort }) {
let startTime = null
let aborted = false
let rafId = null
const promise = new Promise((resolve) => {
function step(now) {
if (aborted) {
onAbort && onAbort()
resolve({ done: false, aborted: true })
return
}
if (startTime === null) startTime = now
const elapsed = now - startTime
const progress = Math.min(elapsed / duration, 1)
const eased = easing(progress)
onUpdate && onUpdate(eased, progress)
if (progress < 1) {
rafId = requestAnimationFrame(step)
} else {
resolve({ done: true, aborted: false })
}
}
rafId = requestAnimationFrame(step)
})
return {
finished: promise,
abort() { aborted = true; cancelAnimationFrame(rafId) },
}
}
使用示例
const anim = animate({
duration: 1000,
easing: t => 1 - Math.pow(1 - t, 3),
onUpdate: (eased) => {
box.style.opacity = eased
box.style.transform = 'translateX(' + (eased * 200) + 'px)'
},
})
setTimeout(() => anim.abort(), 500)
await anim.finished
console.log('动画结束或被中断')
考察点
- 时间戳驱动:用
now - startTime 算进度,避免依赖固定帧间隔,切后台也不会错乱。
- 缓动函数:把线性进度
t 映射成非线性,实现加速 / 减速。
- 可中断:用
cancelAnimationFrame + 标志位,防止动画重复执行和内存泄漏。
- 返回 Promise:调用方可用
await 等待结束或中断。
同分类其他题目