JavaScript 动画:从 setInterval 到 requestAnimationFrame 与可复用的 animate 时序框架(zh.javascript.info 实战指南)

发布时间:2026/10/9 2:29:37

JavaScript 动画:从 setInterval 到 requestAnimationFrame 与可复用的 animate 时序框架(zh.javascript.info 实战指南) 文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载本篇技术指南围绕《现代 JavaScript 教程》zh.javascript.info中 JavaScript 动画一章展开讲解如何用原生 JavaScript 实现 CSS 无法完成的动画——例如沿任意时序函数运动的复杂路径、画布动画等。你将掌握基于requestAnimationFrame的动画原理、一套可复用的animate({timing, draw, duration})时序框架、五种常用时序函数幂次、圆弧、反弹、弹跳、伸缩及其easeOut/easeInOut变换并看到仓库中对应的可运行演示与练习源码。为什么需要 JavaScript 动画从 HTML/CSS 的角度看动画的本质是style 属性的逐渐变化。比如把style.left从0px逐步改为100px元素就会移动。但 CSS 动画transition/keyframes受限于 Bezier 曲线定义的时序无法精确表达“先拉弓再射出”“落体后多次弹跳”这类复杂运动也无法驱动画布canvas上的绘制逻辑。JavaScript 动画可以处理这些 CSS 无法处理的事情沿着与 Bezier 曲线不同的时序函数定义的复杂路径移动实现画布上的动画在draw回调中做任何事——不仅仅是改 CSS 属性还可以动态创建元素如烟花效果。仓库中的文章正文位于 7-animation/3-js-animation/article.md配套的可运行演示都放在同目录的.view子目录中下文将结合这些源码逐一展开。使用 setInterval最朴素的动画实现如果每秒做 50 次小变化动画看起来就会流畅。电影也是同样的原理每秒 24 帧或更多帧足以让人眼感觉平滑。伪代码如下let delay 1000 / 50; // 每秒 50 帧 let timer setInterval(function() { if (animation complete) clearInterval(timer); else increase style.left }, delay)仓库中 move.view/index.html 提供了一个完整的 setInterval 动画示例点击小火车图片让它用 2 秒时间从0px移动到400px。train.onclick function() { let start Date.now(); let timer setInterval(function() { let timePassed Date.now() - start; train.style.left timePassed / 5 px; if (timePassed 2000) clearInterval(timer); }, 20); }把这段代码抽象成一般形式就是文章中的标准写法let start Date.now(); // 保存开始时间 let timer setInterval(function() { // 距开始过了多长时间 let timePassed Date.now() - start; if (timePassed 2000) { clearInterval(timer); // 2 秒后结束动画 return; } // 在 timePassed 时刻绘制动画 draw(timePassed); }, 20); // 随着 timePassed 从 0 增加到 2000 // 将 left 的值从 0px 增加到 400px function draw(timePassed) { train.style.left timePassed / 5 px; }要点Date.now()记录起始时刻每次回调计算已流逝时间timePassed达到总时长后clearInterval结束动画期间每次回调按时间比例更新样式。点击move演示即可看到效果[codetabs height200 srcmove]。使用 requestAnimationFrame让浏览器决定何时重绘setInterval 的两个问题假设同时运行多个动画如果每个动画各自维护一个setInterval(..., 20)那么 20ms 内会有多次相互独立的重绘请求浏览器被迫重复做几何计算与绘制。正确的做法是把多个动画合并进同一个间隔// 更好合并到一次回调中 setInterval(function() { animate1(); animate2(); animate3(); }, 20)// 更差各自独立触发20ms 内多次重绘 setInterval(animate1, 20); setInterval(animate2, 20); setInterval(animate3, 20);第二个问题当 CPU 过载、或浏览器选项卡被隐藏后台标签页时绘图完全没有意义但setInterval依然会持续触发回调、消耗资源。requestAnimationFrame 的语法与行为W3C 的动画时序标准提供了requestAnimationFrame函数它同时解决上述两个问题let requestId requestAnimationFrame(callback);callback会在浏览器每次重绘的最近时间运行通常间隔 10~20ms回调中对元素做的变化会与其他requestAnimationFrame回调和 CSS 动画合并为一次几何计算与重绘页面在后台标签页时根本没有重绘回调不会运行——动画自动暂停不消耗资源返回值requestId用于取消回调cancelAnimationFrame(requestId);callback会收到一个参数——从页面加载开始经过的毫秒数等价于performance.now()的返回值。通常回调很快运行除非 CPU 过载或笔记本电量告急等特殊情况。下面的代码打印前 10 次回调之间的时间间隔读者在浏览器中运行可以看到通常为 10~20msscript let prev performance.now(); let times 0; requestAnimationFrame(function measure(time) { document.body.insertAdjacentHTML(beforeEnd, Math.floor(time - prev) ); prev time; if (times 10) requestAnimationFrame(measure); }); /script仓库还提供了使用requestAnimationFrame重构的小火车演示 move-raf.view/index.html其中内联了一个animate(draw, duration)的早期雏形可对照setInterval版本体会差异function animate(draw, duration) { let start performance.now(); requestAnimationFrame(function animate(time) { let timePassed time - start; if (timePassed duration) timePassed duration; let progress timePassed / duration; // 0 到 1 draw(progress); if (timePassed duration) { requestAnimationFrame(animate); } }); }结构化动画可复用的 animate 框架在requestAnimationFrame基础上文章给出一个更通用的动画函数。它把动画拆成三个正交的维度——时长、时序、绘制从而让“动什么”和“怎么动”完全解耦function animate({timing, draw, duration}) { let start performance.now(); requestAnimationFrame(function animate(time) { // timeFraction 从 0 增加到 1 let timeFraction (time - start) / duration; if (timeFraction 1) timeFraction 1; // 计算当前动画状态 let progress timing(timeFraction); draw(progress); // 绘制 if (timeFraction 1) { requestAnimationFrame(animate); } }); }仓库中的 width.view/animate.js 就是该函数的可运行实现参数顺序写为{duration, draw, timing}语义相同可直接拷贝使用。三个核心参数duration: 动画总时间单位毫秒例如1000。timing(timeFraction): 时序函数类比 CSS 的transition-timing-function。入参是已过去时间与总时间之比0代表开始1代表结束的小数返回动画完成度类似 Bezier 曲线中的y通常也在 0~1 之间。例如线性时序js function linear(timeFraction) { return timeFraction; } 它等价于 transition-timing-function: linear曲线如下 [![linear 时序函数曲线](https://raw.gitcode.com/gh_mirrors/zh/zh.javascript.info/raw/4ae3116347a56fcfed6bbdeb30b76f802c0511a2/7-animation/3-js-animation/linear.svg?utm_sourcegitcode_repo_files)](https://link.gitcode.com/i/2a222f0beac1f9983a02ef0c921952dc)draw(progress): 接收动画完成度progress0为开始状态1为结束状态并执行实际绘制。它可以移动元素js function draw(progress) { train.style.left progress px; } ……也可以做任何其他事情以任何方式为任何事物制作动画。实战把 width 从 0 动画到 100%使用上面的框架让一个进度条元素的宽度从0变化到100%见 width.view/index.html 的完整可运行代码elem.onclick function() { animate({ duration: 1000, timing: function(timeFraction) { return timeFraction; }, draw: function(progress) { elem.style.width progress * 100 %; } }); };与 CSS 动画不同这套方案里时序函数不受 Bezier 曲线限制draw也不局限于修改 CSS 属性——可以为烟花等效果动态创建新元素。时序函数大全n 次幂加速运动想让动画加速可以让progress取n次幂。例如抛物线二次方function quad(timeFraction) { return Math.pow(timeFraction, 2) }三次甚至更大的n会让动画加速得更快。progress为 5 次幂的曲线圆弧function circ(timeFraction) { return 1 - Math.sin(Math.acos(timeFraction)); }反弹弓箭射击“弓箭射击”时序先“拉弓弦”然后“射击”。它依赖附加参数x“弹性系数”定义“拉弓弦”的距离function back(x, timeFraction) { return Math.pow(timeFraction, 2) * ((x 1) * timeFraction - x); }x 1.5时的曲线弹跳想象抛球后落地反弹数次再停下的过程。bounce函数采用相反的顺序弹跳立即开始。它使用了几组特殊系数各系数在for循环中按a b, b / 2迭代决定每一跳的落点与高度function bounce(timeFraction) { for (let a 0, b 1, result; 1; a b, b / 2) { if (timeFraction (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) Math.pow(b, 2) } } }伸缩动画“伸缩”时序附加参数x作为“初始范围”function elastic(x, timeFraction) { return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction) }x 1.5时的曲线以上各时序函数的曲线 SVG 与演示页quad.view、quint.view、circ.view、back.view、bounce.view、elastic.view均位于 7-animation/3-js-animation 目录可在浏览器中直接打开index.html点击体验。逆转时序easeOut 与 easeInOut以上函数直接使用称为easeIn效果在动画开始阶段表现。很多时候需要以相反顺序展示动画即easeOut变换。easeOut数学形式timingEaseOut(timeFraction) 1 - timing(1 - timeFraction)。封装成一个工厂函数// 接受时序函数返回变换后的变体 function makeEaseOut(timing) { return function(timeFraction) { return 1 - timing(1 - timeFraction); } }例如把bounce变成 easeOut 版let bounceEaseOut makeEaseOut(bounce);这样弹跳不在动画开始时执行而是在结束时执行观感更自然。下图展示变换如何改变函数行为——红色为常规弹跳蓝色为 easeOut 弹跳常规弹跳物体在底部弹跳然后突然跳到顶部easeOut变换后物体先跳到顶部然后在顶部弹跳。easeInOut让效果在动画开头和结尾都出现。计算方式if (timeFraction 0.5) { // 动画前半部分 return timing(2 * timeFraction) / 2; } else { // 动画后半部分 return (2 - timing(2 * (1 - timeFraction))) / 2; }封装器function makeEaseInOut(timing) { return function(timeFraction) { if (timeFraction .5) return timing(2 * timeFraction) / 2; else return (2 - timing(2 * (1 - timeFraction))) / 2; } } bounceEaseInOut makeEaseInOut(bounce);“easeInOut” 变换把两段曲线拼在一起前半段是缩小的 “easeIn”后半段是缩小的 “easeOut”动画以相同效果开始和结束。对比circ的三种变体可以看得很清楚——红色为 easeIn常规绿色为 easeOut蓝色为 easeInOut仓库中的 bounce-easeout.view 与 bounce-easeinout.view 是这两种变换的可运行演示配合各自的style.css定义球的落点与场地尺寸。更有趣的 draw不止移动元素只要写出合适的draw就能动画化任何东西。text.view 演示了以弹跳方式逐字落下的文字输入动画——draw在这里操作文本节点而非 CSS 位移。配套练习与源码印证本仓库为该章配了两道实战练习完整答案在对应的solution.view中适合动手验证上文框架为弹跳的球设置动画让球在position:relative的区域内垂直弹跳。核心思路场地底部坐标是field.clientHeight球顶部坐标最底时达到field.clientHeight - ball.clientHeight于是把top从0动画到该差值并配合makeEaseOut(bounce)。最终代码见 solution.mdlet to field.clientHeight - ball.clientHeight; animate({ duration: 2000, timing: makeEaseOut(bounce), draw(progress) { ball.style.top to * progress px } });设置动画使球向右移动在上一题基础上让球同时向右移动100px。水平方向没有“反弹”速度逐渐增加用makeEaseOut(quad)作为时序更自然。两个方向各跑一个animate实例互不干扰见 solution.mdlet height field.clientHeight - ball.clientHeight; let width 100; // 设置 top 动画弹跳 animate({ duration: 2000, timing: makeEaseOut(bounce), draw: function(progress) { ball.style.top height * progress px } }); // 设置 left 动画向右移动 animate({ duration: 2000, timing: makeEaseOut(quad), draw: function(progress) { ball.style.left width * progress px } });这两个练习的完整 HTML 实现含makeEaseOut、bounce、quad内联定义分别在 1-animate-ball/solution.view/index.html 与 2-animate-ball-hops/solution.view/index.html可对照学习。总结JavaScript 动画应通过requestAnimationFrame实现。该内建方法让回调在浏览器准备重绘时运行通常很快但确切时间由浏览器决定页面在后台时没有重绘回调不会运行——动画自动暂停且不消耗资源。推荐的通用 helper 是animatefunction animate({timing, draw, duration}) { let start performance.now(); requestAnimationFrame(function animate(time) { // timeFraction 从 0 增加到 1 let timeFraction (time - start) / duration; if (timeFraction 1) timeFraction 1; // 计算当前动画状态 let progress timing(timeFraction); draw(progress); // 绘制 if (timeFraction 1) { requestAnimationFrame(animate); } }); }三个参数duration为总毫秒数timing接收 0~1 的小数时间、返回 0~1 的进度draw负责实际绘制。JavaScript 动画可以使用任意时序函数幂次、圆弧、反弹、弹跳、伸缩并通过makeEaseOut/makeEaseInOut变换获得更多变体——与 CSS 不同我们不受 Bezier 曲线限制draw同样不受限可以动画化 CSS 之外的任何事物。动画框架本身可以继续改进增加更多能力但 JavaScript 动画通常只用于有趣、不标准的场景建议按需扩展而非过度设计。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐The JavaScript WayJavaScript 网页动画入门——setInterval、setTimeout 与 requestAnimationFrame 实战The JavaScript WayJavaScript 网页动画入门——setInterval、setTimeout 与 requestAnimationF教程文档Modern JavaScript Tutorial 实战用 requestAnimationFrame 与 easeOut(bounce) 时序实现弹跳球动画Modern JavaScript Tutorial 实战用 requestAnimationFrame 与 easeOut bounce 时序实现弹跳球动画文档/教程前端用 requestAnimationFrame 实现弹跳球动画现代 JavaScript 教程 JS 动画实战解析用 requestAnimationFrame 实现弹跳球动画现代 JavaScript 教程 JS 动画实战解析 本文以《现代 JavaScript 教程》文档/教程前端上一篇RT-Thread APM32F407ZG-EVAL BSP 开发指南从零上手极海 APM32F407 评估板下一篇Sunshine 游戏串流终极指南十分钟免费搭建家庭游戏串流中心创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/9 3:19:38

《操作系统》英文笔记(一): Introduction To OS

笔者这学期在澳科大当交流生,故这学期开始在这里分享澳科大这边课程的笔记,先从《操作系统》课程开始吧。What is an Operating System? Most computers have two modes of operation: kernel mode and user mode. OS runs in kernel mode (also called…

2026/10/9 3:19:38

C++优先队列priority_queue详解:从堆原理到Top-K与Dijkstra应用

优先队列(C)这个话题,我确实想好好写一篇。做了这么多年实际项目和算法实现,我一直觉得 STL 里最被低估的容器之一就是std::priority_queue。很多人对vector、map、sort熟得不能再熟,但一提到“动态取最大值/最小值”的…

2026/10/9 3:19:38

前后端分离架构下,团队协作模式如何转型?接口契约与联调实践

这些年我带过不少 Web 项目团队,发现一件特别有意思的事:很多人以为“前后端分离”只是技术架构的升级,换了框架、拆了工程、改了部署方式就完事了。可真把团队拉进去做一两个迭代之后,你会发现最痛的根本不是技术选型&#xff0c…

2026/10/9 3:19:38

HTTP协议深度解析:从400错误到HTTP/3的实战指南

1. 从一次“打不开网页”的故障说起:HTTP 不是教科书里的抽象概念,而是你每次刷新页面时都在真实运行的协议上周帮某高校实验室调试一套远程图像采集系统,设备端能稳定生成JPEG帧,但Web管理界面始终显示“加载中…”——后端日志里…

2026/10/9 3:19:38

PLC培训机构怎么选?实操课多不等于真动手,避坑看这几点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 3:14:38

不用鲁大师!Windows自带工具轻松查看内存条频率与插槽信息

不管你是想给老电脑续命、给刚装好的新机器验货,还是最近总感觉系统卡顿怀疑内存有问题,打开电脑后脑袋里多半都会冒出一串问题:我这条内存到底是多少频率的?现在是跑在双通道上吗?插槽还剩几个?这些信息其…

2026/10/8 10:03:18

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/8 10:03:20

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/8 6:05:44

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 0:04:27

毕业论文初稿完成后首次进行AIGC疑似度自查的摸底与分流策略

毕业论文初稿完成后首次进行AIGC疑似度自查的摸底与分流策略当数万字的学位论文初稿经历开题、实验、问卷与多轮文献梳理最终成形时,绝大多数研究生都会面临一道全新的形式审查关卡:AIGC 疑似度排查。在高校毕业审核流程中,盲审前的文本检测通…

2026/10/9 0:04:27

食堂节能改造源头工厂,商用厨房设备焕新方案广受好评

商用厨房作为餐饮经营、单位供餐的核心后勤阵地,其设备配置、动线规划与运维体系直接决定后厨作业效率、运营成本与合规性。从基础的灶具、制冷存储设备,到油烟净化、水处理等配套系统,每一个环节的合理性都与食品安全、能耗管控、消防安全挂…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑