我会从 “为什么用 JavaScript 动画” 开始,然后深入到 “核心原理”、“主流技术方案”、“性能优化”,最后以 “一个完整的实践案例” 和 “学习资源” 结束。

为什么使用 JavaScript 动画?
在 CSS3 动画如此强大的今天,我们为什么还需要 JavaScript?
- 复杂性和交互性:CSS 难以实现基于用户交互、数据变化或复杂物理模拟的动画,一个根据鼠标移动而倾斜的卡片,或一个数据可视化图表的动态变化。
- 精确控制:JS 可以精确控制动画的每一帧,获取和修改 DOM 元素的任何属性(位置、大小、颜色、透明度,甚至是
clip-path、filter等)。 - 与业务逻辑紧密结合:动画常常是应用状态的一部分,JS 可以轻松地在数据更新时触发动画,或者在动画结束后执行回调函数,更新应用状态。
- 跨浏览器一致性:虽然现代浏览器对 CSS 动画支持很好,但在处理一些复杂或旧版浏览器兼容性问题时,JS(配合库)可以提供更统一的解决方案。
动画的核心原理:帧 与 时间
无论你使用哪种技术,网页动画的本质都是 “欺骗”人眼。
- 帧:动画是由一系列静止的图像快速连续播放而成的,每一幅图像就是一“帧”。
- 刷新率:屏幕每秒刷新的次数,单位是赫兹,常见的有 60Hz(60次/秒)、120Hz 等。
- 帧率:你的动画每秒播放多少帧,为了达到流畅的视觉效果,目标帧率是 60 FPS (Frames Per Second)。
如何实现 60 FPS? 浏览器大约每 16.7 毫秒(1000ms / 60 ≈ 16.7ms)会重绘一次屏幕,如果你的动画更新逻辑能在这个时间内完成,就能达到 60 FPS。
主流的 JavaScript 动画技术方案
从底层到高级,主要有以下几种方案:

requestAnimationFrame (rAF) - 现代标准
这是目前 最推荐、最主流 的实现高性能动画的方式。
-
是什么:一个浏览器提供的 API,用于在下一次浏览器重绘之前调用一个指定的函数,它会自动优化动画的时机,与浏览器的刷新率同步。
-
优点:
- 性能最佳:当浏览器标签页处于非活动状态时,它会自动暂停动画,节省 CPU 和电量。
- 自动同步:无需手动计算时间间隔,直接与浏览器渲染周期绑定。
- 简单易用:API 非常简洁。
-
基本用法:
(图片来源网络,侵删)let element = document.getElementById('my-box'); let position = 0; function animate() { // 更新位置 position += 2; element.style.transform = `translateX(${position}px)`; // 请求下一帧 if (position < 300) { // 简单的停止条件 requestAnimationFrame(animate); } } // 启动动画 requestAnimationFrame(animate);
Web Animations API (WAAPI) - 未来趋势
这是一个更高级、更强大的原生 API,旨在成为 CSS Animations 和 Transitions 的 JavaScript 等价物。
-
是什么:直接在 DOM 元素上创建和控制动画。
-
优点:
- 语法简洁:使用
element.animate()方法,非常直观。 - 功能强大:可以轻松控制时间函数、延迟、迭代次数、播放方向等。
- 性能优越:底层同样由浏览器优化,性能接近 CSS。
- Promise 支持:动画可以返回一个 Promise,方便使用
then()或async/await处理动画结束事件。
- 语法简洁:使用
-
基本用法:
const element = document.getElementById('my-box'); // 定义关键帧 const keyframes = [ { transform: 'translateX(0)' }, { transform: 'translateX(300px)' } ]; // 定义动画选项 const options = { duration: 2000, // 2秒 iterations: Infinity, // 无限循环 direction: 'alternate' // 来回交替 }; // 创建并播放动画 const animation = element.animate(keyframes, options); // 监听动画结束 animation.onfinish = () => console.log('Animation finished!');
CSS + JavaScript 结合 - 最灵活
这是最常见和实用的模式:用 JS 控制动画的触发和状态,用 CSS 来定义动画的具体表现和过渡效果。
-
场景:点击按钮后,给元素添加一个 CSS class,该 class 包含了
transition或animation属性。 -
优点:
- 性能好:将动画的渲染工作交由浏览器高效的 CSS 引擎处理。
- 代码分离:样式和行为分离,代码更清晰。
- 易于维护:修改动画效果只需改 CSS,无需动 JS。
-
基本用法:
<style> #my-box { width: 100px; height: 100px; background-color: blue; /* 定义过渡效果 */ transition: transform 1s ease-in-out, background-color 0.5s; } .is-active { /* 定义最终状态 */ transform: translateX(300px) rotate(360deg); background-color: red; } </style> <div id="my-box"></div> <button id="start-btn">Start Animation</button>const box = document.getElementById('my-box'); const btn = document.getElementById('start-btn'); btn.addEventListener('click', () => { // JS 只负责添加/移除 class box.classList.add('is-active'); // 可以通过 JS 监听过渡结束事件 box.addEventListener('transitionend', (event) => { if (event.propertyName === 'transform') { console.log('Transform transition finished!'); } }); });
第三方动画库 - 功能强大,开箱即用
对于复杂的动画需求,使用成熟的库是最高效的选择。
- GSAP (GreenSock Animation Platform):
- 业界标杆:性能之王,功能极其强大,拥有复杂的动画时间线、缓动函数、滚动动画等。
- 特点:轻量级、高性能、兼容性好。
- anime.js:
- 轻量且优雅:API 设计非常现代和流畅,支持 CSS 属性、SVG、JS 对象等多种属性。
- 特点:学习曲线平缓,功能强大。
性能优化黄金法则
糟糕的动画会导致页面卡顿、掉帧,严重影响用户体验,遵循以下原则:
-
使用
transform和opacity:- 这两个属性由 合成器线程 处理,不会触发整个页面的重排,性能开销极小。
- 避免频繁修改
width,height,margin,top,left等会触发 重排 的属性。
-
使用
will-change提前告知浏览器:will-change是一个 CSS 属性,用于提前告诉浏览器某个元素将要发生变化,让浏览器提前做好准备(为该元素创建新的图层)。- 用法:
.animated-box { will-change: transform, opacity; } - 警告:不要滥用!只在即将发生复杂动画的元素上使用,并在动画结束后移除该属性,否则会占用大量内存。
-
避免在每一帧中进行昂贵的操作:
- 不要在
requestAnimationFrame回调中进行 DOM 查询、复杂的计算或布局抖动。 - 将这些操作放在动画循环之外,或者使用节流/防抖技术。
- 不要在
-
硬件加速:
- 对于移动元素,可以强制浏览器为其创建一个独立的图层,利用 GPU 加速渲染。
- 最简单的方法是:
transform: translateZ(0);或transform: translate3d(0, 0, 0);,这通常会触发硬件加速。
实践案例:创建一个平滑的跟随鼠标的卡片
这个案例结合了 requestAnimationFrame 和 transform,是 JS 动画的经典应用。
目标:一个卡片会平滑地跟随鼠标移动,但有一个延迟效果,看起来更自然。
HTML:
<div class="container">
<div id="card" class="card">
<h2>Follow Me!</h2>
<p>Move your mouse around.</p>
</div>
</div>
CSS:
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #f0f0f0;
overflow: hidden; /* 防止滚动条 */
}
.container {
position: relative;
width: 100%;
height: 100%;
}
.card {
width: 200px;
height: 280px;
background: linear-gradient(135deg, #6e8efb, #a777e3);
border-radius: 15px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
/* 关键:使用 transform 进行动画,性能最佳 */
transform-style: preserve-3d; /* 如果需要 3D 效果 */
transition: box-shadow 0.3s ease; /* 阴影变化用过渡,更柔和 */
}
.card:hover {
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
}
JavaScript:
const card = document.getElementById('card');
// 鼠标在页面上的位置
let mouseX = 0;
let mouseY = 0;
// 卡片的当前位置
let cardX = 0;
let cardY = 0;
// 跟随的平滑系数,值越小,跟随越延迟
const speed = 0.1;
// 监听鼠标移动
document.addEventListener('mousemove', (event) => {
// 更新目标位置(鼠标位置)
mouseX = event.clientX;
mouseY = event.clientY;
});
// 使用 requestAnimationFrame 创建动画循环
function animate() {
// 计算卡片当前位置与目标位置的差值,并乘以一个系数
// 这会产生一个缓动效果,而不是瞬间跳到目标位置
cardX += (mouseX - cardX) * speed;
cardY += (mouseY - cardY) * speed;
// 使用 transform: translate 来移动卡片
// 也可以加上 rotateZ 增加一些趣味性
const rotateX = (mouseY - window.innerHeight / 2) / 25;
const rotateY = (window.innerWidth / 2 - mouseX) / 25;
card.style.transform = `
translate3d(${cardX - window.innerWidth / 2}px, ${cardY - window.innerHeight / 2}px, 0)
rotateX(${rotateX}deg)
rotateY(${rotateY}deg)
`;
// 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画
animate();
学习资源总结
- MDN Web Docs:
- 性能优化:
- 动画库:
- 灵感与学习:
| 技术 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
requestAnimationFrame |
性能最优,与浏览器同步 | 需要手动实现所有动画逻辑 | 自定义、复杂的逐帧动画 |
| Web Animations API | 语法强大,Promise 支持 | 兼容性稍差(IE 不支持) | 需要精细控制 CSS 动画的场景 |
| CSS + JS | 性能好,代码分离 | 动画逻辑较简单时略显繁琐 | 大多数交互式UI动画(hover, click) |
| 第三方库 | 功能强大,开箱即用 | 增加项目体积,学习成本 | 复杂的时间线动画、物理模拟、滚动动画 |
对于初学者,建议从 CSS + JS 结合开始,然后深入理解 requestAnimationFrame 的原理,当你需要处理更复杂的动画时,再考虑学习 GSAP 或 anime.js 这样的库。
