浏览器: 深入理解requestAnimationFrame优化js运行时

先有问题再有答案

  1. rAF是什么?
  2. rAF是在当前帧执行还是在下一帧执行?
  3. rAF在一帧的执行时机是什么?
  4. rAF属于宏任务还是微任务?
  5. raf和js任务, 渲染流程什么关系?
  6. 如何利用rAF实现一个nextFrame的功能?
  7. requestAnimationFrame有什么用?

前置文章

浏览器:帧&事件循环
浏览器:帧&渲染流程
js性能优化:时间切片分帧,webworker并行, requestidlecallback空闲执行,延迟执行
js三座大山之异步一单线程,event loop,宏任务&微任务

rAF是什么

window.requestAnimationFrame() 告诉浏览器------你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

rAF是浏览器提供的和渲染帧率保持同步的一个api.
注意是渲染帧率不是屏幕刷新率哦两者的区别可以看这篇文章: js三座大山之异步五基于异步的js性能优化

例子:

javascript 复制代码
// 获取需要动画处理的元素
const element = document.getElementById('animateMe');

// 初始化位置和速度
let position = 0;
const speed = 5; // 每帧移动5像素

function moveElement() {
    // 更新元素的位置
    position += speed;
    element.style.left = `${position}px`;

    // 如果元素还未移动到500px的位置,继续动画
    if (position < 500) {
        window.requestAnimationFrame(moveElement);
    }
}

// 启动动画
window.requestAnimationFrame(moveElement);

调用时机

相信上面的图片 大家应该都不陌生了。

根据这个图片 我们的理解是raf是在当前帧的js执行结束后,渲染流程前被调用的。

我也找了挺多资料的 里面提到了如下代码 可以验证raf的执行时机。

ini 复制代码
test.style.transform = 'translate(0, 0)';

document.querySelector('button').addEventListener('click', () => {
  const test = document.querySelector('.test');
  test.style.transform = 'translate(400px, 0)';
  
  requestAnimationFrame(() => {
    test.style.transition = 'transform 3s linear';
    test.style.transform = 'translate(200px, 0)';
  });
});

代码地址: safari-raf-bug.glitch.me/

运行效果:
chrome 版本124.0.6367.62(正式版本)

safari:版本14.0.3 看起来chrome和safari都是一样的 会被推迟到下一帧的渲染流程前去执行。 貌似和规范相比不一致

Firefox: 版本125.0.2 (64 位) Firefox的表现和规范的描述是一致的。

看起来浏览器的实现和 规范 并不完全一致,raf运行时机可以归纳为在js执行后,在当前帧或下一帧渲染流程前,具体和浏览器有关

我们可以实现一个nextFrameExecute函数 确定使其运行在下一帧, 类似下面这样的。

javascript 复制代码
export const nextFrameExecute = async (task: Task) => {
  let _resolve: Resolve;
  let _reject: Resolve;
  const p = new Promise((resolve, reject) => {
    _resolve = resolve;
    _reject = reject;
  });

  if (typeof requestAnimationFrame !== "undefined") {
    // 浏览器兼容 确保推迟到下一帧
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        Promise.resolve(task()).then(_resolve, _reject);
      })
    });
    return p;
  }

  setTimeout(() => {
    Promise.resolve(task()).then(_resolve, _reject);
  }, 16);
  return p;
};

代码来自: github.com/wuyunqiang/...

rAF属于宏任务还是微任务?

首先这个问题就不应该这样问,它实际上属于浏览器的渲染机制的一部分,并拥有自己的特定时机和队列。它既不是宏任务也不是微任务

非要归类到这两个中 那么rAF的执行时机和表现特性类似于宏任务

作用

js运行时性能优化

我们可以将一个长任务拆分为N多个小的任务,这些操作将在浏览器的每一帧中执行。通过这样做,可以避免长时间执行脚本造成的主线程阻塞,从而防止界面冻结和用户交互延迟。

详情参考:js性能优化:时间切片分帧,webworker并行, requestidlecallback空闲执行,延迟执行~~

无优化的效果 运行长任务

优化后分帧运行

与浏览器的渲染同步,优化动画的运行时性能

rAF 为更新动画提供了一个最佳时机------就在浏览器准备重绘之前。这可以确保在屏幕更新的每一帧中都使用最新的动画状态,避免动画的跳帧或不必要的重绘(和重回)、提高动画流畅性和视觉效果。这一点对于实现流畅的用户界面动效至关重要。

节能省电

当用户切换到其他标签或最小化窗口时,浏览器可能不会执行 rAF 回调,从而减少 CPU 和 GPU 的使用,节省电能。这对于移动设备和电池供电的设备尤其有利。

减少页面布局抖动

通过在适当的时间进行 DOM 更新,rAF 可以帮助减少由于频繁的样式和布局变更引发的抖动问题,这是因为所有的变更都是在浏览器绘制之前完成的。

参考

  1. requestanimationframe-scheduling-for-nerds
  2. react_issues
  3. mdn_requestAnimationFrame
  4. youtube
  5. requestAnimationFrame回调时机
  6. rAF
  7. html.spec
相关推荐
y先森10 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy11 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891114 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端