夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript文章

    • 33个非常实用的JavaScript一行代码
    • new命令原理
    • ES5面向对象
    • ES6面向对象
    • 多种数组去重性能对比
    • 获取ip地址
    • ES6扩展符运用
    • file、base64和blob互相转化
    • 图片转base64格式
    • 实现异步队列
    • websocket实现二维码扫描
    • js动画之requestAnimationFrame
      • 使用方法
        • requestAnimationFrame的基本语法
        • 如何在动画中使用requestAnimationFrame
        • requestAnimationFrame的回调函数参数
      • 性能优化
        • 避免在requestAnimationFrame回调函数中进行大量计算
        • 使用硬件加速优化动画性能
        • 如何在不同设备上实现平滑的动画效果
      • 与其他动画库的比较
        • requestAnimationFrame与setTimeout/setInterval的区别
        • requestAnimationFrame与CSS动画的结合使用
      • 实际应用
        • 使用requestAnimationFrame实现常见动画效果
        • requestAnimationFrame在游戏开发中的应用
        • requestAnimationFrame在响应式设计中的应用
        • requestAnimationFrame优化滚动监听
        • requestAnimationFrame 优化列表滚动
        • requestAnimationFrame 优化监听
      • 兼容性和后续发展
        • requestAnimationFrame的浏览器兼容性
        • requestAnimationFrame的未来发展趋势
        • 如何在不支持requestAnimationFrame的浏览器中实现类似效果
      • 总结
    • JS随机打乱数组
    • 判断是否为移动端浏览器
    • 将一维数组按指定长度转为二维数组
    • 防抖与节流函数
    • 时间戳与倒计时
    • JS获取和修改url参数
    • 比typeof运算符更准确的类型判断
    • 前端下载excel文件
    • 商品sku算法
    • 数组与对象的互相转化
    • Javascript原生事件监听及手动触发
    • JS编码与转码
    • 树结构与扁平化相互转换
    • 实现微信登录
    • 判断两个对象值是否相等
    • 常用表单验证
    • 在H5中实现OCR拍照识别身份证功能
    • 滚动条元素定位
    • 获取目标容器内的元素列表
    • 微信H5支付
    • 对象除空
    • 获取指定链接的参数
    • js中的函数注释
    • 大模型流式数据前端实现
    • 定高虚拟列表
    • 不定高虚拟列表
    • 虚拟表格
    • 移动端文件预览
    • 浏览器缓存机制
    • 前端从剪切板获取word图片
  • 前端架构

  • 学习笔记

  • 全栈项目

  • 前端
  • JavaScript文章
千年老妖
2024-07-10
目录

js动画之requestAnimationFrame

# js动画之requestAnimationFrame

什么是requestAnimationFrame?

requestAnimationFrame是浏览器用于定时循环操作的一个API,通常用于动画和游戏开发。它会把每一帧中的所有DOM操作集中起来,在重绘之前一次性更新,并且关联到浏览器的重绘操作。

# 为什么使用requestAnimationFrame而不是setTimeout或setInterval

与setTimeout或setInterval相比,requestAnimationFrame具有以下优势:

  • 通过系统时间间隔来调用回调函数,无需担心系统负载和阻塞问题,系统会自动调整回调频率。
  • 由浏览器内部进行调度和优化,性能更高,消耗的CPU和GPU资源更少。
  • 避免帧丢失现象,确保回调连续执行,实现更流畅的动画效果。
  • 自动合并多个回调,避免不必要的开销。
  • 与浏览器的刷新同步,不会在浏览器页面不可见时执行回调。

# requestAnimationFrame的优势和适用场景

requestAnimationFrame最适用于需要连续高频执行的动画,如游戏开发,数据可视化动画等。它与浏览器刷新周期保持一致,不会因为间隔时间不均匀而导致动画卡顿。

# 使用方法

# requestAnimationFrame的基本语法

requestAnimationFrame接收一个回调函数作为参数,该回调函数会在浏览器下一次重绘前执行。

const animation = () => {
  // 执行动画
  requestAnimationFrame(animation); 
}

requestAnimationFrame(animation); 
1
2
3
4
5
6

上面代码会不停调用requestAnimationFrame,在每次浏览器重绘前执行回调函数,实现连续动画效果。

# 如何在动画中使用requestAnimationFrame

可以在回调函数里更新动画的状态,然后清除上一帧,绘制新状态的这一帧:

let angle = 0; 

const render = () => {
  ctx.clearRect(0, 0, width, height); // 清除上一帧

  // 更新状态
  angle += delta;
  
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, angle); 
  ctx.stroke();

  requestAnimationFrame(render);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这样通过在每次回调中更新参数,就可以实现对象的连续动画了。

# requestAnimationFrame的回调函数参数

requestAnimationFrame的回调函数会收到一个参数,这个参数是一个时间戳,单位为毫秒,代表requestAnimationFrame被触发的时间。可以根据这个时间戳计算两帧的时间间隔,来调整动画速度。

let prevTimestamp;
const render = timestamp => {
  if (!prevTimestamp) prevTimestamp = timestamp;
  const delta = timestamp - prevTimestamp;

  // 根据时间间隔计算速度
  x += speed * delta;

  prevTimestamp = timestamp;
  requestAnimationFrame(render);
}
1
2
3
4
5
6
7
8
9
10
11

# 性能优化

# 避免在requestAnimationFrame回调函数中进行大量计算

由于requestAnimationFrame的回调会在一个高优先级的线程中被同步执行,如果回调函数中有大量计算,会导致此线程被阻塞,从而引起页面卡顿。

也就是如果 requestAnimationFrame 的回调函数执行时间超过一帧(通常是 16.67 毫秒,因为浏览器通常以每秒约 60 帧的速度刷新),则可能会导致动画性能下降,可能出现掉帧的情况,最终影响用户体验。这是因为浏览器每帧的时间是有限的,如果回调函数执行时间过长,就会导致下一帧的准备和绘制时间受到压缩,导致动画卡顿。

通常,应该尽量避免在 requestAnimationFrame 的回调函数中执行耗时的操作。为了解决这个问题,可以采取以下一些策略:

  1. 优化回调函数: 使回调函数尽可能简短,避免不必要的计算或循环。在回调中只执行与动画相关的必要操作。
  2. 分帧处理: 如果动画需要处理大量数据或计算复杂的操作,可以将这些操作分散到多个 requestAnimationFrame 回调中,以避免长时间的占用。
  3. Web Workers: 将耗时的计算放在独立的 Web Worker 线程中执行,以不影响主线程和动画渲染。
  4. 帧率控制: 如果回调函数耗时较长,可以根据回调函数的实际执行时间来控制动画的帧率。减小动画对象的速度或者减少渲染质量,以适应当前性能。
  5. 监测性能: 使用浏览器开发者工具来监测性能,以找出哪些操作导致了回调函数执行时间过长。

总之,确保 requestAnimationFrame 回调函数的执行时间尽量短,以确保动画的流畅性和性能。

# 使用硬件加速优化动画性能

启用GPU加速渲染,可以显著提升动画性能。

.animated {
  transform: translateZ(0); /* 开启硬件加速 */  
}
1
2
3

# 如何在不同设备上实现平滑的动画效果

可根据requestAnimationFrame回调的时间戳,计算这一帧与上一帧的间隔时间delta,并根据delta的值采取不同的优化手段:

  • delta特别小,说明这一帧花费时间过长,可能导致掉帧,可以降低动画对象的移动速度或图像质量
  • delta逐渐变大,说明动画逐渐卡顿,可以降低动画对象数量或复杂度
  • delta波动较大,说明系统资源不足,可以采用简单的动画作为降级策略

# 与其他动画库的比较

# requestAnimationFrame与setTimeout/setInterval的区别

  • setTimeout/setInterval是固定时间间隔触发, requestAnimationFrame依赖系统刷新调度
  • setTimeout/setInterval会无视页面是否可见, requestAnimationFrame会停止刷新
  • setTimeout/setInterval难以避免丢帧问题,requestAnimationFrame与刷新同步避免丢帧

# requestAnimationFrame与CSS动画的结合使用

requestAnimationFrame可用于更新动画状态,实现复杂动画逻辑,而CSS动画用于声明式定义动画的样式变化,两者可配合实现更丰富的动画效果。

# 实际应用

# 使用requestAnimationFrame实现常见动画效果

可使用 requestAnimationFrame 实现对象轨迹动画、SVG图形动画、加载动画等效果。

// 小球拖尾效果
const positions = [];

const render = () => {
  // 添加新位置
  positions.push({x, y});

  if (positions.length > 10){
    positions.shift(); 
  }

  // 渲染小球
  ctx.clearRect(0, 0, width, height);
  positions.forEach(pos => ctx.fillRect(pos.x, pos.y, 10, 10));

  requestAnimationFrame(render);
}
// SVG绘制动画
let length = 0;

const render = () => {
  length += 4;

  svgLine.setAttribute("stroke-dasharray", length);

  if (length < 300) {
    requestAnimationFrame(render);
  }
}

requestAnimationFrame(render); 
// 进度条加载动画  
let progress = 0;

const render = () => {
  progress += 1; 
  loadBar.style.width = progress + '%';

  if(progress < 100) {
    requestAnimationFrame(render);
  }
}

requestAnimationFrame(render);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# requestAnimationFrame在游戏开发中的应用

游戏需要非常流畅的画面和实时的响应,这正是requestAnimationFrame的优势,它可用于实现游戏中的物体移动、碰撞检测、帧数控制等操作。

// 飞机射击动画
const update = () => {
  // 子弹位置更新
  bullets.forEach(bullet => {
    bullet.position += speed;
  })

  // 飞机位置更新
  aircraft.position += delta * speed;

  // 绘制所有元素
  render(bullets, aircraft);
  
  requestAnimationFrame(update);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# requestAnimationFrame在响应式设计中的应用

可使用requestAnimationFrame来更平滑地执行响应式布局的变化,避免布局突然大幅移动带来的视觉冲击感。

let width = 500;

const resize = () => {
  width = container.clientWidth;

  box.style.width = width + "px";

  requestAnimationFrame(resize);
}

window.addEventListener("resize", resize);
1
2
3
4
5
6
7
8
9
10
11

# requestAnimationFrame优化滚动监听

window.addEventListener('scroll', e => {
    window.requestAnimationFrame(timestamp => {
        animation(timestamp)
    })
})
1
2
3
4
5
let scheduledAnimationFrame=false;
const onScroll = e => {
    if (scheduledAnimationFrame) { return }

    scheduledAnimationFrame = true
    window.requestAnimationFrame(timestamp => {
        scheduledAnimationFrame = false
        animation(timestamp)
    })
}
window.addEventListener('scroll', onScroll)
1
2
3
4
5
6
7
8
9
10
11

# requestAnimationFrame 优化列表滚动

let start;
let timeOff = false;
const scrollFrame = (timestramp) => {
  if (start === undefined) {
    start = timestramp;
  }
  const elapsed = timestramp - start;
  const offset = getOffset({
    scrollTop,
    offsetTop,
    elapsed,
    duration,
    extraDistance,
  });
  if (duration - elapsed >= 0) {
    window.requestAnimationFrame(scrollFrame);
    scolling(scroller, offset + extraDistance, documentSelector);
  } else {
    // 最后一帧因为时间判断可能不会执行,补上最后一帧
    if (!timeOff) {
      scolling(scroller, offsetTop + extraDistance, documentSelector);
      timeOff = true;
    }
  }
};

function getOffset(){
//...
}
window.requestAnimationFrame(scrollFrame);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# requestAnimationFrame 优化监听

class AnimationDebounce {
  resizeRAF  = null;
  run(callback) {
    if (this.resizeRAF) return;
    this.resizeRAF = requestAnimationFrame(() => {
      callback?.();
      this.resizeRAF = null;
    });
  }
}
const AnimationFrame=new AnimationDebounce()
window.addEventListener('resize', AnimationFrame.run(instance.refresh))
1
2
3
4
5
6
7
8
9
10
11
12

# 兼容性和后续发展

# requestAnimationFrame的浏览器兼容性

requestAnimationFrame现在已经得到了广泛支持,可以直接使用。对于不支持的浏览器,可以用setTimeout模拟requestAnimationFrame。

window.requestAnimationFrame = window.requestAnimationFrame ||
                               window.webkitRequestAnimationFrame ||
                               (cb => setTimeout(cb, 1000/60));
1
2
3

# requestAnimationFrame的未来发展趋势

未来requestAnimationFrame可能会支持设置帧率、增强调度算法等,提升动画性能。Web工作者线程也可带来更多优化空间。

浏览器厂商也在继续改进相关API,比如setTimeout和requestIdleCallback也在朝着更精确的调度方向发展。

# 如何在不支持requestAnimationFrame的浏览器中实现类似效果

可以通过自己实现一个polyfill:

window.requestAnimationFrame = window.requestAnimationFrame || 
                               window.webkitRequestAnimationFrame ||
                               (cb => setTimeout(cb, 1000/60));
1
2
3

这样就可以在大多数浏览器中使用 requestAnimationFrame 了。对于老旧浏览器,可以使用 setInterval 模拟,但效果会比较粗糙。

# 总结

requestAnimationFrame是实现复杂动画的好帮手,必须掌握其用法与优化技巧,才能发挥其最大效用。同时结合其他技术如CSS动画、Web Worker等也可以实现更好的性能。随着浏览器的不断进步,requestAnimationFrame还具有很大的拓展潜力。

编辑 (opens new window)
上次更新: 2025/4/3 18:13:01
websocket实现二维码扫描
JS随机打乱数组

← websocket实现二维码扫描 JS随机打乱数组→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式