为什么你的动画卡成PPT?requestAnimationFrame才是真香!

前端攻城狮们,有没有遇到过这样的尴尬:精心设计的动画效果,一上线就卡成PPT,用户反馈“你们的页面怎么一抖一抖的”?

别急着甩锅给浏览器或者用户电脑配置差,问题可能出在你用的定时器上!今天我们就来聊聊为什么requestAnimationFrame才是做动画的“天选之子”,而setTimeout可能正在默默坑你。

为什么不用setTimeout做动画?

先来说个真实案例。去年我有个朋友接了个电商网站项目,首页有个商品轮播图动画,他用setTimeout实现了,测试时候好好的,上线后却收到一堆投诉说“滑动卡顿”。

他一开始以为是图片太大,压缩了所有图片后问题依旧。最后才发现罪魁祸首竟然是setTimeout!

setTimeout做动画有两个致命伤:

第一,它不关心浏览器的刷新周期。大多数显示器的刷新率是60Hz,也就是每16.7ms刷新一次。但setTimeout只能设置一个固定时间间隔,比如16ms,这就很容易和屏幕刷新节奏错开。

想象一下,你在跳舞,音乐节奏是固定的,但你的舞步总是慢半拍或者快半拍,能不别扭吗?

第二,当页面被隐藏或最小化时,setTimeout还在后台傻傻地执行,白白消耗CPU和电量。用户手机发烫了,还以为是在挖矿呢!

requestAnimationFrame为什么是王道?

requestAnimationFrame(后面我们就叫它rAF吧)是浏览器专门为动画提供的API,它解决了setTimeout的所有痛点。

rAF最大的优势就是:它会在浏览器下一次重绘之前执行回调函数,完美契合显示器的刷新节奏。

还是用跳舞的比喻,这次rAF会先听音乐的节奏,然后精准地踩在每一个拍子上,视觉效果自然流畅多了。

而且当页面不可见时,rAF会自动暂停执行,不会浪费任何资源,这才是真正的“智能省电模式”啊!

手把手教你用rAF实现动画循环

理论知识说够了,来点实际的!下面是一个最简单的rAF动画示例:

function animate() {
  // 这里是你的动画逻辑
  element.style.left = (parseInt(element.style.left) + 1) + 'px';
  
  // 循环调用
  requestAnimationFrame(animate);
}

// 启动动画
requestAnimationFrame(animate);

看到没?就是这么简单!但实际项目中我们通常需要控制动画的开始和结束:

let animationId;
let startTime;

function animate(timestamp) {
  if (!startTime) startTime = timestamp;
  
  // 计算动画进度
  const progress = timestamp - startTime;
  
  // 执行动画,例如移动100px,用时1000ms
  element.style.transform = `translateX(${Math.min(progress / 1000 * 100, 100)}px)`;
  
  if (progress < 1000) {
    animationId = requestAnimationFrame(animate);
  }
}

// 开始动画
function startAnimation() {
  animationId = requestAnimationFrame(animate);
}

// 停止动画
function stopAnimation() {
  cancelAnimationFrame(animationId);
}

这里用了timestamp参数,它是rAF回调函数自动传入的时间戳,精度高达微秒级别,比Date.now()精准多了!

性能优化小技巧

虽然rAF已经很优秀了,但用好它还需要一些技巧:

1. 避免在rAF中做耗时操作

rAF的执行频率很高,如果在这里面做大量计算或者DOM操作,还是会卡。复杂计算最好放在Web Worker中。

2. 批量DOM操作

频繁修改DOM样式会触发重排和重绘,最好先计算好所有值,一次性应用。

function animate() {
  // 先计算...
  const left = computeLeft();
  const top = computeTop();
  
  // ...再一次性应用
  element.style.cssText = `left: ${left}px; top: ${top}px;`;
  
  requestAnimationFrame(animate);
}

3. 使用transform和opacity

这两个属性不会触发重排,只触发重绘,性能开销小得多。能用transform就别用left/top。

实战案例:平滑滚动到顶部

现在我们来个实战案例,用rAF实现一个平滑滚动到页面顶部的功能:

function scrollToTop() {
  const duration = 1000; // 滚动时间
  const start = window.pageYOffset;
  const startTime = performance.now();

  function animate(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    
    // 使用easeOut缓动函数让滚动更自然
    const easeOut = progress => progress * (2 - progress);
    
    window.scrollTo(0, start * (1 - easeOut(progress)));
    
    if (progress < 1) {
      requestAnimationFrame(animate);
    }
  }

  requestAnimationFrame(animate);
}

这个实现比直接使用scrollTo({behavior: ‘smooth’})的兼容性更好,而且可以自定义滚动时长和缓动效果。

兼容性处理

虽然现代浏览器都支持rAF,但如果你还需要支持老古董浏览器,可以加上兼容代码:

window.requestAnimationFrame = window.requestAnimationFrame ||
                               window.mozRequestAnimationFrame ||
                               window.webkitRequestAnimationFrame ||
                               window.msRequestAnimationFrame ||
                               function(callback) {
                                 return setTimeout(callback, 1000 / 60);
                               };

window.cancelAnimationFrame = window.cancelAnimationFrame ||
                              window.mozCancelAnimationFrame ||
                              function(id) {
                                clearTimeout(id);
                              };

这样即使在不支持rAF的浏览器中,也会回退到setTimeout,虽然效果差些,但至少不会报错。

总结

requestAnimationFrame不是万能的,但在做动画方面,它确实比setTimeout强太多了。就像你不能用螺丝刀去锤钉子一样,选择合适的工具才能事半功倍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值