在requestAnimationFrame之前,使用js实现一个动画一般是用定时器来实现:
<div id="box"></div>
const box = document.getElementById('box')
const distance = 500 // 目标移动距离
const duration = 2000 // 运动持续时间
const fps = 60 // 每秒60帧
const interval = 1000 / fps // 每帧的时间间隔
const step = distance / (duration / interval) // 每帧移动的距离
let currentPosition = 0
let inter = null
function boxAnimate() {
currentPosition += step
box.style.transform = `translateX(${currentPosition}px)`
console.log('currentPosition', currentPosition)
// 停止动画
if (currentPosition >= distance && inter) {
clearInterval(inter)
inter = null
}
}
startBtn.addEventListener('click', function () {
inter = setInterval(boxAnimate, interval)
})
使用requestAnimationFrame实现:
<div id="box1"></div>
const box1 = document.getElementById('box1')
let start = null
function box1Animate(timestamp) {
console.log('timestamp', timestamp)
if (!start) start = timestamp
const progress = timestamp - start
// 计算新的位置
box1.style.transform = `translateX(${Math.min(
(progress / duration) * distance,
distance
)}px)`
// 如果动画未完成,请求下一帧
if (progress < duration) {
requestAnimationFrame(box1Animate)
}
}
startBtn.addEventListener('click', function () {
// 启动动画
requestAnimationFrame(box1Animate)
})
乍一看好像也没啥区别啊,但这2种实现方式有本质区别:
- 定时器不能保证与刷新率同步(计时器准确性问题),可能会导致丢帧,造成页面卡顿;
requestAnimationFrame是基于浏览器的重绘周期来执行动画,每次屏幕刷新时调用一次,一般情况下,显示屏的刷新率为60hz(每秒60帧),差不多每隔16.7ms调用一次,当显示器的刷新率变化时,requestAnimationFrame可以自动匹配最新的刷新率,非常强大 - 当页面隐藏时(例如切到其他标签页),定时器还会继续执行,
requestAnimationFrame会自动暂停
因此,requestAnimationFrame有很高的流畅度,它在css3动画实现不了的时候,具有很高的便利性,可以更好地结合canvas或webGL中
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>移动动画</title>
<style>
#box {
width: 50px;
height: 50px;
background-color: lightblue;
}
#box1 {
width: 50px;
height: 50px;
background-color: gold;
}
</style>
</head>
<body>
<button id="startBtn">开始</button>
<button id="resetBtn">重置</button>
<div id="box"></div>
<div id="box1"></div>
<script>
const box = document.getElementById('box')
const distance = 500 // 目标移动距离
const duration = 2000 // 运动持续时间
const fps = 60 // 每秒60帧
const interval = 1000 / fps // 每帧的时间间隔
const step = distance / (duration / interval) // 每帧移动的距离
let currentPosition = 0
let inter = null
let animationId = null
function boxAnimate() {
currentPosition += step
box.style.transform = `translateX(${currentPosition}px)`
console.log('currentPosition', currentPosition)
// 停止动画
if (currentPosition >= distance && inter) {
clearInterval(inter)
inter = null
}
}
const box1 = document.getElementById('box1')
let start = null
function box1Animate(timestamp) {
console.log('timestamp', timestamp)
if (!start) start = timestamp
const progress = timestamp - start
// 计算新的位置
box1.style.transform = `translateX(${Math.min(
(progress / duration) * distance,
distance
)}px)`
// 如果动画未完成,请求下一帧
if (progress < duration) {
animationId = requestAnimationFrame(box1Animate)
} else {
if (animationId) {
cancelAnimationFrame(animationId)
}
}
}
startBtn.addEventListener('click', function () {
inter = setInterval(boxAnimate, interval)
// 启动动画
animationId = requestAnimationFrame(box1Animate)
})
resetBtn.addEventListener('click', function () {
window.location.reload()
})
</script>
</body>
</html>