
文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载JavaScript 动画可以处理 CSS 无法胜任的场景沿复杂路径移动、使用贝塞尔曲线之外的任意时序函数、在 canvas 上绘制动画等。本文以 7-animation/3-js-animation/article.md 为核心脉络结合仓库中的真实示例move.view、width.view、bounce.view、text.view等完整讲解从setInterval逐帧动画到requestAnimationFrame结构化动画的进阶之路并深入剖析animate通用函数、七类 Timing 函数与easeOut/easeInOut变换读完即可动手实现弹跳、回弹、弹性、打字机等专业级动画效果。为什么需要 JavaScript 动画CSS 动画transition与keyframes简单高效但有明显的边界移动路径被限制为直线或贝塞尔曲线时序函数被限制为有限的预设缓动且无法在 canvas 上做逐帧绘制。JavaScript 动画则没有这些限制——它把每一帧的绘制权完全交给开发者路径可以是任意复杂曲线如圆周、正弦波、组合路径时序函数可以是任意数学函数不受贝塞尔曲线约束draw阶段可以修改任何东西CSS 属性、canvas 像素、DOM 节点增删甚至模拟烟花粒子系统。本质上动画就是一串帧——通常是 HTML/CSS 属性的微小连续变化。例如把style.left从0px递增到100px只要每帧步进足够小如2px、刷新足够快约每秒 50 帧人眼就会感知为平滑运动。这与电影的原理一致每秒 24 帧即可获得平滑观感。第一版方案用 setInterval 逐帧驱动最直接的实现是用setInterval周期性修改属性值。伪代码如下let timer setInterval(function() { if (动画已完成) clearInterval(timer); else 将 style.left 增加 2px }, 20); // 每 20ms 变化 2px约每秒 50 帧仓库中 move.view/index.html 给出了完整的可运行版本——点击小火车图片它会在 2 秒内从左侧滑到右侧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); }文中的完整示例则用Date.now()记录起始时间每次回调计算已流逝时间timePassed超过 2 秒2000ms即clearInterval(timer)终止否则调用draw(timePassed)绘制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; }setInterval 方案的三个缺陷多动画帧不齐假设同时运行 3 个动画各自调用setInterval(..., 20)由于启动时间不同每 20ms 的节拍彼此错位浏览器在同一个 20ms 窗口内可能触发多次独立重绘。把多次重绘合并为一次能显著降低 CPU 负载并让画面更流畅// 轻量一次回调内完成所有动画 setInterval(function() { animate1(); animate2(); animate3(); }, 20)// 较重三个独立定时器、多次独立重绘 setInterval(animate1, 20); setInterval(animate2, 20); setInterval(animate3, 20);无法感知浏览器空闲CPU 过载、标签页被隐藏等情况下浏览器理应降低重绘频率但setInterval依然严格按20ms触发白白消耗资源。背景标签页空转页面不可见时无需渲染setInterval却仍在后台运行。第二版方案requestAnimationFrame 与浏览器刷新同步针对上述问题W3C 的 Animation Timing 规范提供了requestAnimationFrame——它把回调调度到浏览器下一次准备重绘的时刻执行从根本上与渲染管线对齐。语法与取消let requestId requestAnimationFrame(callback)callback会在浏览器最接近准备做动画的时刻运行在同一帧内所有requestAnimationFrame回调会与 CSS 动画一起被分组浏览器只做一次几何计算与重绘而不是多次返回的requestId可用于取消本次调度cancelAnimationFrame(requestId);回调参数页面加载以来的毫秒时间callback接收一个参数——从页面加载开始到当前的时间毫秒也可以通过performance.now()获取同一时间源。仓库 move-raf.view/index.html 展示了基于它的完整动画function animate(draw, duration) { let start performance.now(); requestAnimationFrame(function animate(time) { // 从开始到现在过去了多少时间 let timePassed time - start; if (timePassed duration) timePassed duration; // progress 是 0 到 1 的数值表示流逝时间占比 let progress duration / timePassed; // 绘制动画进度 draw(progress); // 时间未到——继续调度下一帧 if (timePassed duration) { requestAnimationFrame(animate); } }); }文档还给出了一个测量脚本连续记录前 10 次requestAnimationFrame回调的间隔通常为 10–20ms对应 60Hz 刷新率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); });正常情况下回调会很快触发只有当 CPU 过载、笔记本电量即将耗尽等特殊情况下才会延迟。页面处于后台时浏览器完全不重绘回调不会运行动画自动暂停且不消耗任何资源——这是setInterval无法媲美的关键优势。结构化动画通用 animate 函数将requestAnimationFrame的骨架抽象出来就得到文档与仓库共用的核心工具函数。仓库中的 width.view/animate.js 是它的真实实现function animate({duration, draw, timing}) { let start performance.now(); requestAnimationFrame(function animate(time) { let timeFraction (time - start) / duration; if (timeFraction 1) timeFraction 1; let progress timing(timeFraction) draw(progress); if (timeFraction 1) { requestAnimationFrame(animate); } }); }三个参数的本质参数含义取值约定duration动画总时长毫秒如1000表示 1 秒timing(timeFraction)时序函数接收流逝时间占比起点0终点1返回动画完成度类似 CSStransition-timing-function返回值相当于贝塞尔曲线上的y值draw(progress)绘制函数接收动画完成度0起点、1终点并执行实际绘制可以移动元素也可以做任何事timing与draw的职责分离是本函数的设计精髓timing只回答现在应该进行到百分之多少draw只负责把某个进度画出来二者通过 0~1 的归一化数值解耦可任意组合复用。最简单的线性时序函数——动画匀速进行function linear(timeFraction) { return timeFraction; }其图形见 linear.svg等价于transition-timing-function: linear。最简单的绘制函数——移动元素function draw(progress) { train.style.left progress px; }当然也可以绘制任何内容动画对象与方式完全不受限。实战让宽度从 0 生长到 100%文档给出了利用该函数把元素width从0动画到100%的完整代码示例见 width.viewanimate({ duration: 1000, timing(timeFraction) { return timeFraction; }, draw(progress) { elem.style.width progress * 100 %; } });相比 CSS 动画这里timing不受贝塞尔曲线限制draw甚至可以越过 CSS 属性去创建新元素如烟花动画。Timing 函数库从抛物线到弹簧时序函数决定动画的性格。文档逐一介绍了 7 类函数每一类在仓库中都有独立的.view示例与.svg图形。幂函数族n 次方加速要做出越来越快的加速感把progress取n次幂即可。二次抛物线quadfunction quad(timeFraction) { return Math.pow(timeFraction, 2) }图形见 quad.svg实际效果见 quad.view。继续增大指数可让加速更剧烈五次幂 quint.svg 与 quint.viewfunction quint(timeFraction) { return Math.pow(timeFraction, 5); }圆弧函数circ前半段极速前进、临近终点骤然放缓的冲浪曲线function circ(timeFraction) { return 1 - Math.sin(Math.acos(timeFraction)); }图形见 circ.svg效果见 circ.view。回弹函数back拉弓射箭拉弓蓄力再发射——动画先向反方向回退一段再冲向终点。它比前几个函数多一个参数x弹性系数决定拉弓的距离function back(x, timeFraction) { return Math.pow(timeFraction, 2) * ((x 1) * timeFraction - x) }x 1.5时的图形见 back.svg效果见 back.view。弹跳函数bounce落地反弹模拟球体落地后反弹数次再停住的过程。函数内部使用一组特殊系数分段计算function bounce(timeFraction) { for (let a 0, b 1; 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) } } }效果见 bounce.view——仓库实现把bounce直接内联进animate调用配合brick.style.left progress * 500 px让方块带弹跳地滑过 500px。弹性函数elastic弹簧振荡带初始振幅参数x的弹性衰减振荡function elastic(x, timeFraction) { return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction) }x 1.5时的图形见 elastic.svg效果见 elastic.view。反转变换easeOut 与 easeInOut上面这些函数直接使用即为easeIn效果发生在动画开头。很多场景需要把效果搬到动画结尾这不需要重写函数只需一个数学包装器。easeOut把效果镜像到结尾timingEaseOut的数学定义是对称反转1 - timing(1 - timeFraction)。通用变换函数makeEaseOut接收任意普通时序函数返回其反转版本function makeEaseOut(timing) { return function(timeFraction) { return 1 - timing(1 - timeFraction); } }对bounce应用它弹跳就从开头挪到了结尾效果反而更自然let bounceEaseOut makeEaseOut(bounce);示例见 bounce-easeout.view。bounce-inout.svg 清晰对比了两种行为红色为普通bounce对象先在底部弹跳、结尾猛然跳到顶部蓝色为easeOut版本先冲上顶部、再在顶部弹跳。easeInOut开头结尾双重效果让效果同时出现在开头和结尾。按时间中点分段前半段执行timing后半段执行其镜像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);效果见 bounce-easeinout.view。本质是把两张图拼接成一张前半段是缩小的easeIn后半段是缩小的easeOut。circ-ease.svg 用circ函数对比了三种模式红色easeIn、绿色easeOut、蓝色easeInOut——动画以同样的效果开始与结束。放飞想象力任意 draw 函数draw不必只改 CSS 属性。文档给出的弹跳打字机示例见 text.view把bounce时序与文本切片结合每次回调用text.slice(0, Math.ceil(result))重新赋值文本框内容让文字以弹跳节奏逐字蹦出来function animateText(textArea) { let text textArea.value; let to text.length, from 0; animate({ duration: 5000, timing: bounce, draw: function(progress) { let result (to - from) * progress from; textArea.value text.slice(0, Math.ceil(result)) } }); }配套实战任务本章配套两道由易到难的练习对应 1-animate-ball 与 2-animate-ball-hops把本文知识串联成完整动画。任务一弹跳的小球让小球在场地内垂直弹跳。关键点是坐标系换算小球使用position:absolutetop定位而 CSStop指的是球的上边缘所以终点应为field.clientHeight - ball.clientHeight。结合makeEaseOut(bounce)即可得到自然的落地反弹let to field.clientHeight - ball.clientHeight; animate({ duration: 2000, timing: makeEaseOut(bounce), draw(progress) { ball.style.top to * progress px } });完整运行效果见 1-animate-ball/solution.view。任务二边弹跳边向右移动在任务一基础上叠加水平位移。垂直方向继续用makeEaseOut(bounce)水平方向用makeEaseOut(quad)获得先快后慢的推进感——两个animate各自独立驱动一个属性互不干扰这正是requestAnimationFrame天然支持多动画并发的原因let height field.clientHeight - ball.clientHeight; let width 100; // 垂直弹跳 animate({ duration: 2000, timing: makeEaseOut(bounce), draw: function(progress) { ball.style.top height * progress px } }); // 水平向右移动 animate({ duration: 2000, timing: makeEaseOut(quad), draw: function(progress) { ball.style.left width * progress px } });效果见 2-animate-ball-hops/solution.view。总结何时选 JavaScript 动画CSS 无法胜任复杂路径、非贝塞尔时序、canvas 逐帧或需要精细控制时。必须用requestAnimationFrame回调由浏览器安排在重绘前执行同一帧内所有回调与 CSS 动画合并为一次布局与重绘页面在后台时不重绘、回调不运行动画自动挂起、零资源消耗。通用animate函数duration时长、timing0→1 的进度映射、draw绘制完成度三参数解耦覆盖绝大多数动画需求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); } }); }时序函数可任意定制quad/quint加速、circ冲刺、back拉弓回弹、bounce落地反弹、elastic弹性振荡再经makeEaseOut/makeEaseInOut变换即可获得全部缓动形态——完全不受贝塞尔曲线限制。draw同样无限不止 CSS 属性文本、canvas、DOM 结构都可以是动画目标。当然这个animate只是最小骨架你完全可以在需要时按需扩展添加取消支持、循环次数、延迟启动、缓动预设库等。JavaScript 动画并非日常高频需求它的价值在于实现有趣且非标准的效果——把这份骨架与函数库保存好用到时取来即可。本文配套的全部可运行示例move.view、width.view、back.view、bounce.view、elastic.view、text.view、bounce-easeout.view、bounce-easeinout.view等与时序函数图形linear.svg、quad.svg、quint.svg、circ.svg、back.svg、elastic.svg、bounce-inout.svg、circ-ease.svg均位于仓库 7-animation/3-js-animation 目录下可直接打开体验或作为二次开发起点。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐Bootstrap-select 下拉动画效果CSS 过渡与自定义动画函数Bootstrap select 下拉动画效果CSS 过渡与自定义动画函数 你是否还在为下拉菜单生硬的显示/隐藏效果而烦恼用户点击时毫无过渡的弹出、选择后突前端UI组件如何创建超越系统动画的ViewAnimator自定义Easing函数终极指南如何创建超越系统动画的ViewAnimator自定义Easing函数终极指南 ViewAnimator是一个强大的iOS动画库通过简洁的链式API帮助开发者移动开发Swift Foundation文件管理系统详解FileManager与路径操作终极指南Swift Foundation文件管理系统详解FileManager与路径操作终极指南 Swift Foundation框架中的 FileManager文件上一篇OmniRoute 测试覆盖率提升计划解读从 57% 到 90% 的分阶段覆盖率治理方案下一篇PaddleOCR 中文数据集完全指南六大常用数据集详解与训练数据准备实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考