JavaScript事件循环(下) - requestAnimationFrame与Web Workers

JavaScript事件循环(下) - requestAnimationFrame与Web Workers

如何实现丝滑流畅的 60fps 动画?如何在单线程 JavaScript 中实现真正的并行计算?本篇文章将探索事件循环的高阶应用。

前言:从60fps的动画说起

在 JavaScript 中,常见的动画实现方式有以下三种:

使用setInterval(不推荐)

functionanimateWithSetInterval(){setInterval(()=>{updateAnimation();renderFrame();},16.67);}

上述代码试图达到60fps(1000/60 ≈ 16.67ms),但定时器不精确,可能丢帧或过度绘制。

递归setTimeout

functionanimateWithSetTimeout(){functionloop(){updateAnimation();renderFrame();setTimeout(loop,16.67);}loop();}

这种方式比 setInterval 稍好,但仍可能和屏幕刷新不同步。

使用requestAnimationFrame(推荐)

functionanimateWithRAF(){functionloop(timestamp){updateAnimation(timestamp);renderFrame();requestAnimationFrame(loop);}requestAnimationFrame(loop);}

优势:自动匹配屏幕刷新率,节省资源。

requestAnimationFrame:动画的黄金标准

什么是requestAnimationFrame?

requestAnimationFrame(简称 rAF) 是浏览器专门为动画和连续视觉更新提供的 API。它的核心特点是:

  • 在浏览器下一次重绘之前调用指定的回调函数,确保动画与屏幕刷新同步。

rAF的基本用法

functionanimate(){// 更新动画状态updateAnimation();// 渲染当前帧renderFrame();// 请求下一帧requestAnimationFrame(animate);}// 启动动画循环requestAnimationFrame(animate);

rAF的优势

  1. 自动匹配显示器刷新率(通常是60Hz)
  2. 页面不可见时自动暂停,节省资源
  3. 浏览器可以优化动画性能
  4. 提供精确的时间戳参数

rAF的工作原理

functionexperimentRAF(){ console.log('实验开始');// 记录帧数let frameCount =0;let lastTimestamp =0;functionframeCallback(timestamp){ frameCount++;// 计算帧间隔if(lastTimestamp >0){const interval = timestamp - lastTimestamp; console.log(`第${frameCount}帧,间隔: ${interval.toFixed(2)}ms`);} lastTimestamp = timestamp;if(frameCount <10){requestAnimationFrame(frameCallback);}else{ console.log('实验结束,平均帧率:',(1000/((timestamp - startTime)/10)).toFixed(1),'fps');}}const startTime = performance.now();requestAnimationFrame(frameCallback);}

rAf 的关键点在于:frameCallback() 回调中的 timestamp 参数,这个 timestampperformance.now() 返回的高精度时间,也表示回调开始执行的时间。

rAF在事件循环中的位置

setTimeout(()=>{ console.log('1. setTimeout - 宏任务'); Promise.resolve().then(()=>{ console.log('2. setTimeout中的微任务');});},0); Promise.resolve().then(()=>{ console.log('3. Promise - 微任务');requestAnimationFrame(()=>{ console.log('4. Promise中注册的rAF');});});requestAnimationFrame(()=>{ console.log('5. 直接注册的rAF');setTimeout(()=>{ console.log('6. rAF中注册的setTimeout');},0);});queueMicrotask(()=>{ console.log('7. queueMicrotask - 微任务');}); console.log('8. 同步代码');

上述代码的输出顺序如下:

  • 8.同步代码
  • 3.Promise - 微任务
  • 7.queueMicrotask - 微任务
  • 1.setTimeout - 宏任务
  • 2.setTimeout中的微任务
  • 5.直接注册的rAF
  • 4.Promise中注册的rAF
  • 6.rAF中注册的setTimeout

其执行过程如下:

  1. 执行宏任务 (setTimeout, 事件回调等)
  2. 执行微任务 (Promise, queueMicrotask等)
  3. 执行rAF回调 (动画更新)
  4. 样式计算和布局
  5. 绘制 (Paint)
  6. 合成 (Composite)
  7. 检查空闲,执行 requestIdleCallback 回调

Web Workers:真正的多线程编程

什么是Web Workers?

Web Workers 允许 JavaScript 在后台线程中运行脚本,而不会阻塞主线程。这意味着我们可以执行CPU密集型任务,而不会影响页面的响应性。

// 主线程代码 console.log('主线程: 开始');// 创建一个Workerconst worker =newWorker('worker.js');// 向Worker发送消息 worker.postMessage({type:'CALCULATE',data:{numbers:[1,2,3,4,5]}});// 接收Worker的消息 worker.onmessage=(event)=>{const result = event.data; console.log('主线程: 收到Worker结果', result);// 更新UI document.getElementById('result').textContent =`结果: ${result}`;};// 处理Worker错误 worker.onerror=(error)=>{ console.error('Worker错误:', error);}; console.log('主线程: 继续执行其他任务...');

Worker的限制:

  1. 无法访问DOM
  2. 无法使用window、document等
  3. 不能执行同步的XHR(可以使用fetch)
  4. 有同源策略限制
  5. 不能加载本地文件(file://协议)

Web Workers的类型

1. 专用Worker (Dedicated Worker)

只能被创建它的脚本使用:

const dedicatedWorker =newWorker('dedicated-worker.js');

2. 共享Worker (Shared Worker)

可以被多个脚本共享(同源):

if(window.SharedWorker){const sharedWorker =newSharedWorker('shared-worker.js');// 通过port通信 sharedWorker.port.onmessage=(event)=>{ console.log('收到共享Worker消息:', event.data);}; sharedWorker.port.postMessage('Hello Shared Worker');}else{ console.log('浏览器不支持Shared Worker');}

3. Service Worker

用于离线缓存、推送通知等:

if('serviceWorker'in navigator){ navigator.serviceWorker.register('service-worker.js').then(registration=>{ console.log('Service Worker注册成功:', registration);}).catch(error=>{ console.error('Service Worker注册失败:', error);});}

4. Audio Worklet (Chrome 66+)

用于高性能音频处理:

if(window.audioContext && window.audioContext.audioWorklet){ audioContext.audioWorklet.addModule('audio-processor.js').then(()=>{ console.log('Audio Worklet加载成功');});}

5. Paint Worklet (CSS Houdini)

用于自定义CSS绘制:

if(CSS.paintWorklet){CSS.paintWorklet.addModule('paint-worklet.js').then(()=>{ console.log('Paint Worklet加载成功');});}

requestIdleCallback:空闲期任务调度

什么是requestIdleCallback?

requestIdleCallback (简称 rIC )允许开发者在浏览器空闲时期调度任务。这对于执行低优先级或非紧急的工作非常有用,避免影响关键的用户交互和动画。

const idleCallbackId =requestIdleCallback((deadline)=>{ console.log('空闲回调开始执行');// deadline对象包含重要信息: console.log('剩余时间:', deadline.timeRemaining(),'ms'); console.log('是否超时:', deadline.didTimeout);// 在空闲时间内执行任务while(deadline.timeRemaining()>0&&hasMoreWork()){doSomeLowPriorityWork();}// 如果还有工作未完成,再次安排if(hasMoreWork()){requestIdleCallback(processLowPriorityWork);} console.log('空闲回调结束');},{timeout:1000});// 设置超时,确保在1秒内执行// 主线程继续执行其他任务 console.log('主线程继续执行...');

rIC的关键特点:

  1. 只在浏览器空闲时执行
  2. 提供deadline对象,包含剩余时间信息
  3. 可以设置timeout确保执行
  4. 适合低优先级、可中断的任务

rIC在事件循环中的位置

// 理解rIC的执行时机 console.log('=== 事件循环中各API的执行时机 ===');setTimeout(()=>{ console.log('1. setTimeout - 宏任务');},0); Promise.resolve().then(()=>{ console.log('2. Promise - 微任务');});requestAnimationFrame(()=>{ console.log('3. requestAnimationFrame - 动画帧回调');// 在rAF中安排rICrequestIdleCallback(()=>{ console.log('5. rAF中安排的rIC - 空闲回调');},{timeout:100});});requestIdleCallback(()=>{ console.log('4. 直接安排的rIC - 空闲回调');// 在rIC中安排微任务 Promise.resolve().then(()=>{ console.log('6. rIC中的Promise - 微任务');});},{timeout:100});queueMicrotask(()=>{ console.log('7. queueMicrotask - 微任务');}); console.log('8. 同步代码');

上述代码的输出顺序如下:

  • 8.同步代码
  • 2.Promise - 微任务
  • 7.queueMicrotask - 微任务
  • 1.setTimeout - 宏任务
  • 3.requestAnimationFrame - 动画帧回调
  • 4.直接安排的rIC - 空闲回调
  • 6.rIC中的Promise - 微任务
  • 5.rAF中安排的rIC - 空闲回调

其执行过程如下:

  1. 执行宏任务 (setTimeout, 事件回调等)
  2. 执行微任务 (Promise, queueMicrotask等)
  3. 执行rAF回调 (动画更新)
  4. 样式计算和布局
  5. 绘制 (Paint)
  6. 合成 (Composite)
  7. 检查空闲时间,如果有空闲,则执行rIC回调;否则等待下一帧。

核心概念总结

requestAnimationFrame (rAF):

  • 是什么:浏览器提供的动画API,在每次重绘前执行回调
  • 为什么用:自动匹配显示器刷新率,页面不可见时暂停,节省资源
  • 最佳时机:视觉更新、动画、连续状态变化
  • 执行位置:在微任务之后,重绘之前

Web Workers:

  • 是什么:允许JavaScript在后台线程运行的技术
  • 为什么用:执行CPU密集型任务而不阻塞主线程
  • 限制:无法访问DOM,通过消息传递通信
  • 类型:专用Worker、共享Worker、Service Worker等

requestIdleCallback (rIC):

  • 是什么:在浏览器空闲时调度任务的API
  • 为什么用:执行低优先级、非紧急任务
  • 关键对象:deadline包含剩余时间和超时信息
  • 执行位置:在一帧的最后,如果有空闲时间

结语

本文简单介绍了requestAnimationFrameWeb WorkersrequestIdleCallback 的基本用法和对比,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!

Read more

Flutter 组件 ews 的适配 鸿蒙Harmony 实战 - 驾驭企业级 Exchange Web Services 协议、实现鸿蒙端政企办公同步与高安通讯隔离方案

Flutter 组件 ews 的适配 鸿蒙Harmony 实战 - 驾驭企业级 Exchange Web Services 协议、实现鸿蒙端政企办公同步与高安通讯隔离方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 ews 的适配 鸿蒙Harmony 实战 - 驾驭企业级 Exchange Web Services 协议、实现鸿蒙端政企办公同步与高安通讯隔离方案 前言 在鸿蒙(OpenHarmony)生态进军政企办公领域的过程中,与现有企业信息化基础设施的深度集成是一道必答题。即便是在全连接、分布式的今天,微软的 Exchange 服务器依然是全球无数大厂与政务系统处理邮件、日历同步的核心底座。 对于习惯了简单 http.get 的移动开发者来说,Exchange Web Services(EWS)协议由于其复杂的 SOAP 封装、繁琐的 XML 数据结构以及极其严苛的身份认证机制,往往是一块难啃的“骨头”。 ews 库为 Dart 提供了成熟的、类型安全的

前端微前端架构:大项目的救命稻草还是自找麻烦?

前端微前端架构:大项目的救命稻草还是自找麻烦? 毒舌时刻 微前端?听起来就像是一群前端工程师为了显得自己很高级,特意发明的复杂术语。不就是把一个大应用拆成几个小应用嘛,至于搞得这么玄乎吗? 你以为拆成微前端就能解决所有问题?别做梦了!到时候你会发现,调试变得更麻烦了,部署变得更复杂了,甚至连样式都可能互相冲突。 为什么你需要这个 1. 大型应用的可维护性:当你的应用变得越来越大,单靠一个团队已经无法高效维护时,微前端可以让不同团队独立开发和部署各自的模块。 2. 技术栈的灵活性:不同的微前端可以使用不同的技术栈,比如一个模块用React,另一个模块用Vue,这样可以根据团队的专长选择最合适的技术。 3. 独立部署:微前端可以独立部署,不需要整个应用一起发布,这样可以减少发布风险,加快发布速度。 4. 团队协作:不同团队可以独立开发各自的微前端,减少代码冲突和沟通成本。 反面教材 // 这是一个典型的单体应用结构 import React from 'react'; import ReactDOM from 'react-dom'

MaxKB 新手保姆级教程:从零到一,亲手搭建你的专属 AI 知识库助手

MaxKB 新手保姆级教程:从零到一,亲手搭建你的专属 AI 知识库助手

你是否曾想过,能拥有一个只回答你自己领域知识的 AI 聊天机器人?一个能 7x24 小时为客户解答产品问题、为公司员工提供内部资料查询的智能客服?MaxKB 就是这样一款强大且开源的工具,它能帮助你轻松实现这个想法。 本文是一篇面向新手的、极其详尽的指南。将手把手带你完成 MaxKB 的安装、配置,并深入讲解如何创建和优化你的知识库,最后还将详细拆解其最强大的“高级应用”功能,让你真正掌握这个利器。 一、安装 MaxKB:三步搞定,小白也能行 对于新手而言,服务器环境配置往往是第一道坎。别担心,我们选用宝塔面板来简化一切操作。 1. 2. 执行安装命令 Docker 环境就绪后,点击面板左侧的 终端,这会打开一个命令输入窗口。复制以下这行命令,粘贴进去,然后按下回车键。 准备 Docker 环境 登录你的宝塔面板,在左侧菜单栏中找到并点击 docker。如果你是第一次使用,系统会提示你安装

Flutter Web 混合开发:构建跨平台 Web 应用

Flutter Web 混合开发:构建跨平台 Web 应用 代码如诗,Web 如画。让我们用 Flutter Web 的强大能力,构建出既美观又高性能的跨平台 Web 应用。 什么是 Flutter Web? Flutter Web 是 Flutter 框架的 Web 支持,它允许开发者使用 Flutter 的 UI 框架和 Dart 语言来构建 Web 应用。Flutter Web 将 Dart 代码编译为 JavaScript,使其能够在浏览器中运行。 Flutter Web 的优势 1. 单一代码库:一套代码可以同时构建 Web、移动端和桌面端应用。