requestAnimationFrame 动画API详解
什么是requestAnimationFrame?
requestAnimationFrame(简称RAF)是浏览器提供的一个专门用来优化网页动画的api。它的核心作用是让浏览器在下次页面重绘之前执行我们指定的动画代码,这样就能保证动画和屏幕刷新保持同步,让动画效果更加流畅。
为什么选择requestAnimationFrame?
主要优势:
自动匹配屏幕刷新率:通常是60帧/秒(每16.7毫秒执行一次)
智能节能:当页面在后台标签页或窗口最小化时,自动暂停动画
性能更好:浏览器可以优化多个动画,合并到一次重绘中
时间更准:比setTimeout/setInterval控制动画时间更精确
基本使用方法
// 请求动画帧
const animationId = requestAnimationFrame(callback);
// 取消动画帧
cancelAnimationFrame(animationId);回调函数会收到一个时间戳参数,表示当前帧的时间:
function callback(timestamp) {
// timestamp是从页面加载开始到现在的毫秒数
// 在这里更新动画状态
// 如果需要继续动画,再次请求下一帧
requestAnimationFrame(callback);
}实际应用场景
1. 基础动画循环
let startTime;
let lastTime;
function animate(timestamp) {
// 初始化开始时间
if (!startTime) startTime = timestamp;
if (!lastTime) lastTime = timestamp;
// 计算时间差(用于基于时间的动画)
const elapsed = timestamp - startTime;
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
// 更新动画状态(比如位置、透明度等)
const progress = elapsed / 1000; // 转换成秒
const position = progress * 100; // 每秒移动100像素
// 应用动画效果
element.style.transform = `translateX(${position}px)`;
// 如果动画没结束,继续请求下一帧
if (position < 500) { // 假设总共移动500像素
requestAnimationFrame(animate);
}
}
// 开始动画
requestAnimationFrame(animate);2. 滚动事件优化
let ticking = false;
function updateScroll() {
// 处理滚动事件的逻辑
console.log('当前滚动位置:', window.scrollY);
ticking = false;
}
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(updateScroll);
ticking = true;
}
});这种方法可以避免滚动事件触发太频繁导致的性能问题。
3. 带缓动效果的动画
function easeoutQuad(t) {
return t * (2 - t);
}
function animateElement(element, start, end, duration) {
const startTime = performance.now();
function animation(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeProgress = easeOutQuad(progress);
const currentValue = start + (end - start) * easeProgress;
element.style.opacity = currentValue;
if (progress < 1) {
requestAnimationFrame(animation);
}
}
requestAnimationFrame(animation);
}
// 使用例子:让元素慢慢显示出来
animateElement(myElement, 0, 1, 1000); // 1秒内从完全透明变成完全不透明4. 游戏主循环
class GameLoop {
constructor() {
this.lastTime = 0;
this.animationId = null;
}
start() {
this.animationId = requestAnimationFrame(this.loop.bind(this));
}
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
loop(timestamp) {
const deltaTime = timestamp - this.lastTime;
this.lastTime = timestamp;
// 更新游戏状态
this.update(deltaTime);
// 绘制游戏画面
this.render();
// 继续循环
this.animationId = requestAnimationFrame(this.loop.bind(this));
}
update(deltaTime) {
// 更新游戏逻辑
}
render() {
// 绘制游戏画面
}
}浏览器兼容性
支持的浏览器版本:
Chrome 24+
Firefox 23+
Safari 6.1+
Edge 12+
IE 10+
兼容旧浏览器的解决方案:
对于不支持requestAnimationFrame的老浏览器(比如IE9),可以用下面的代码:
// requestAnimationFrame兼容处理
window.requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60); // 大约60帧/秒
};
// cancelAnimationFrame兼容处理
window.cancelAnimationFrame = window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
function(id) {
window.clearTimeout(id);
};使用注意事项
1. 保持回调函数简洁
不要在动画回调里做太复杂的计算,只处理必要的动画逻辑,避免卡顿。
2. 及时取消不需要的动画
用cancelAnimationFrame停止不再需要的动画,防止内存泄漏。
3. 注意时间精度
回调函数收到的时间戳比Date.now()更精确。
4. 后台标签页行为
当页面不在当前标签页时,浏览器可能会降低或暂停动画执行。
5. 避免频繁触发重排
在动画中少用offsetWidth这类会触发页面重新布局的属性。
6. 优先使用transform和opacity
这两个属性变化不会触发重排,性能更好。
7. 复杂动画考虑分层
对复杂动画元素使用will-change属性,提示浏览器优化。
8. iOS设备特殊处理
在iOS设备上,滚动时会暂停requestAnimationFrame,直到滚动停止。
9. 避免多层嵌套
不要在一个动画回调里嵌套多个新的requestAnimationFrame,可能导致动画延迟。
10. 监控性能
复杂动画可以用performance.mark()和performance.measure()来监控性能。
实用技巧
计算帧率:
let frameCount = 0;
let lastTime = performance.now();
function checkFPS(timestamp) {
frameCount++;
if (timestamp >= lastTime + 1000) {
const fps = Math.round((frameCount * 1000) / (timestamp - lastTime));
console.log(`当前帧率: ${fps}FPS`);
frameCount = 0;
lastTime = timestamp;
}
requestAnimationFrame(checkFPS);
}
requestAnimationFrame(checkFPS);动画暂停和继续:
class Animator {
constructor() {
this.animationId = null;
this.isPaused = false;
this.pauseTime = 0;
}
start() {
this.isPaused = false;
this.animationId = requestAnimationFrame(this.animate.bind(this));
}
pause() {
this.isPaused = true;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
animate(timestamp) {
if (this.isPaused) return;
// 动画逻辑...
this.animationId = requestAnimationFrame(this.animate.bind(this));
}
}与setTimeout/setInterval对比
| 特性 | requestAnimationFrame | setTimeout/setInterval |
|---|---|---|
| 执行时机 | 浏览器重绘前 | 固定时间间隔 |
| 频率控制 | 匹配屏幕刷新率 | 固定,不考虑渲染 |
| 后台行为 | 自动暂停 | 继续执行 |
| 时间精度 | 更高 | 受主线程影响 |
| 动画效果 | 更流畅 | 可能卡顿 |
总结
requestAnimationFrame是实现高性能网页动画的最佳选择。它能和浏览器渲染机制很好地配合,提供更流畅的用户体验,是现代前端动画开发的重要工具。
记住关键点:保持动画回调简洁、及时取消不需要的动画、优先使用transform和opacity属性。掌握这些技巧,你就能写出性能更好的网页动画。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!