浏览器: 深入理解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
相关推荐
Qian Xiaoo3 分钟前
前后端分离开发 和 前端工程化
前端
要加油哦~18 分钟前
vue · 插槽 | $slots:访问所有命名插槽内容 | 插槽的使用:子组件和父组件如何书写?
java·前端·javascript
先做个垃圾出来………27 分钟前
split方法
前端
前端Hardy1 小时前
HTML&CSS:3D图片切换效果
前端·javascript
异常君1 小时前
高并发数据写入场景下 MySQL 的性能瓶颈与替代方案
java·mysql·性能优化
spionbo1 小时前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
全宝1 小时前
✏️Canvas实现环形文字
前端·javascript·canvas
lyc2333331 小时前
鸿蒙Core File Kit:极简文件管理指南📁
前端
我这里是好的呀1 小时前
全栈开发个人博客12.嵌套评论设计
前端·全栈