大白话学习性能优化requestAnimationFrame

192 阅读5分钟

前言

  • 常网IT源码上线啦!
  • 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
  • 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
  • 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。

要明白客户需求啊。
东航的竞争对手,是国航吗?看上去,好像是的。但其实,东航真正的对手,可能是那个能用更便宜的票价、更舒适的体验、更准点的时间,把一个乘客从上海送到北京的高铁。当乘客发现,坐高铁的综合体验,比坐飞机更好时,他就会用脚投票,抛弃航空公司。航空公司,不是被另一家航空公司打败的。它是被一个“更好地满足了客户需求”的解决方案打败的。

1.jpg

轮船坐到对面,很惬意。

一、前言

说到性能优化,想讲讲requestAnimationFrame。

rAF是浏览器提供的专门用于动画渲染的API,它会在下一次浏览器重绘之前调用指定的回调函数。当显示器刷新率为60Hz时,浏览器会以约16.7ms(1000ms/60)的间隔触发rAF回调,从而实现60FPS的流畅动画效果。

function animate() {
  // 动画逻辑
  element.style.transform = `translateX(${position}px)`;
  
  position += 2;
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);
  • 垂直同步协调:rAF 与显示器刷新率同步,避免画面撕裂

  • 智能节流:后台标签页自动暂停执行,节省CPU/GPU资源

  • 批处理优化:浏览器将DOM操作合并到同一渲染周期

特性requestAnimationFramesetTimeout
刷新率同步✅ 自动匹配屏幕刷新率❌ 固定间隔(可能不同步)
后台标签页暂停✅ 自动暂停执行❌ 持续执行
电池使用优化✅ 智能优化能耗❌ 无优化机制
帧率自适应✅ 支持高刷新率屏幕(60/120Hz)❌ 固定帧率

直入正文。

二、60FPS是什么

人眼感知阈值:

  • 15FPS:基本可识别为连续画面

  • 30FPS:传统影视标准

  • 60FPS:流畅交互的黄金标准

  • 120FPS:专业游戏体验

布局抖动

// 反例:强制同步布局
element1.style.width = '100px';
const width = element2.offsetWidth; // 强制布局
element3.style.height = width + 'px';

// 正例:批量读写
requestAnimationFrame(() => {
  element1.style.width = '100px';
  const width = element2.offsetWidth;
  element3.style.height = width + 'px';
});

图层管理策略

.animating-element {
  will-change: transform; /* 创建独立图层 */
  transform: translateZ(0); /* GPU加速 */
}

三、nextTick 与 requestAnimationFrame

nextTick签名如下:function nextTick(callback?: () => void): Promise<void>

她是一个promise,所以我们可以await他,也可以放在回调函数中执行。

nextTick 和 requestAnimationFrame 都是异步调度机制:

  • Vue.nextTick:
    在 DOM 更新循环之后 执行回调,主要用于在 Vue 数据变化后操作更新后的 DOM

  • requestAnimationFrame:
    在 浏览器下一次重绘之前 执行回调,专为动画设计,与浏览器刷新率同步(通常为 60FPS)

nextTick 是 Vue 的数据-DOM 同步机制

requestAnimationFrame 是浏览器的渲染时序控制机制

nextTick 解决的是'看到'的问题,requestAnimationFrame 解决的是'流畅'的问题。

完整执行顺序

  1. 执行宏任务(script 整体代码)

  2. 清空微任务队列(Promise、nextTick)

  3. 执行渲染操作(rAF 回调、布局、绘制)

  4. 等待下一事件循环

3.png

他两干活,工作不累。

在涉及 DOM 更新的动画场景中组合使用两者

  1. 数据变更

  2. Vue 的 DOM 更新队列

  3. nextTick 回调(微任务)

  4. requestAnimationFrame(下一帧渲染前)

  5. 浏览器绘制

// 最佳实践:DOM 更新后执行动画
Vue.component('example', {
  methods: {
    updateData() {
      this.data = newData
      
      Vue.nextTick(() => {
        // DOM 已更新
        requestAnimationFrame(() => {
          // 执行基于新 DOM 的动画
          element.animate([...], {duration: 300})
        })
      })
    }
  }
})

更新dom是异步吗?

js的更新dom是同步的,但vue修改数据后,并不马上修改,而是加入一个刷新队列(涉及到事件循环)。当你去获取修改后的值时,实际上vue还没有去修改dom,那自然取不到值。要想取到值,就得用nextTick,把回调函数放在dom更新之后执行,自然取得到值。

为啥不直接更新dom,因为修改dom可能会导致重排回流。

diff算法做法也是不想修改dom,才设计此算法。

四、浏览器兼容性处理

nextTick

// Vue 实际源码中的降级方案
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
} else if (typeof MutationObserver !== 'undefined') {
  // 使用 MutationObserver
} else if (typeof setImmediate !== 'undefined') {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

rAF 的 polyfill

// 兼容旧版浏览器
window.requestAnimationFrame = window.requestAnimationFrame || 
  window.webkitRequestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  function(callback) {
    return setTimeout(() => {
      callback(performance.now())
    }, 16)
  }

至此撒花~

后记

现在或多或少对requestAnimationFrame和nextTick有所了解了叭~

我们在实际项目中或多或少遇到一些奇奇怪怪的问题。

自己也会对一些写法的思考,为什么不行🤔,又为什么行了?

最后,祝君能拿下满意的offer。

我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

以往推荐

为什么一定要有微任务,直接一个宏任务不行吗

深入Vue3/React Hooks

聊聊小程序的双线程架构

Vue性能优化:从加载提速到运行时优化

vue2和Vue3和React的diff算法展开说说:从原理到优化策略

前端哪有什么设计模式

小小导出,我大前端足矣!

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

VuePress搭建项目组件文档

原文链接

juejin.cn/post/753839…