动画本质是定时修改CSS属性,需用requestAnimationFrame实现60fps同步,配合transform/opacity避免重排,结合缓动函数提升质感,并注意及时终止与帧率适配。

动画的本质是定时修改样式
JavaScript 动画不是“播放一个视频”,而是通过高频(通常每秒 60 次)反复更新元素的 CSS 属性(比如 transform、opacity),利用人眼视觉暂留形成连续运动效果。关键不在于“动”,而在于“怎么动得顺、不卡、可控制”。
requestAnimationFrame 是现代动画的起点
别用 setTimeout 或 setInterval 做动画——它们不和屏幕刷新同步,容易掉帧、抖动、耗电。必须用 requestAnimationFrame,它会把回调安排在浏览器下一次重绘前执行,天然契合 60fps 节奏。
实操要点:
- 每次执行完动画逻辑后,**必须主动再调一次**
requestAnimationFrame,否则只执行一帧 - 传入的回调函数会收到一个时间戳参数(
DOMHighResTimeStamp),可用于计算真实经过时间,避免因帧率波动导致速度不一致 - 用
cancelAnimationFrame主动终止,尤其在组件卸载或用户交互中断时,否则可能引发内存泄漏
let animationId;
function animate(timestamp) {
// 计算 delta 时间(毫秒),用于匀速/缓动逻辑
if (!lastTime) lastTime = timestamp;
const delta = timestamp - lastTime;
lastTime = timestamp;
// 更新元素位置:例如 translateX += speed * (delta / 16)
element.style.transform = translateX(${x}px);
// 继续下一帧
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
用 transform 和 opacity 避免重排(reflow)
直接改 left、top、width 等会触发浏览器重新计算布局(reflow),开销大、易卡顿。应优先使用仅触发重绘(repaint)甚至合成(composite)的属性:
立即学习“Java免费学习笔记(深入)”;
-
transform: translateX()、translateY()、scale()、rotate() opacity- 确保目标元素有独立的合成层(可通过
will-change: transform或transform: translateZ(0)触发,但别滥用)
错误示例:element.style.left = x + 'px' —— 强制重排,动画一卡就明显。
缓动函数决定动画的“质感”
线性运动(linear)生硬,真实感强的动画需要缓动(easing)。自己写简单缓动如 easeOutQuad 很容易,没必要总引入库:
function easeOutQuad(t) {
// t ∈ [0, 1],返回 [0, 1] 的插值比例
return t * (2 - t);
}
// 使用示例:从 0 到 100px,持续 500ms
const start = 0;
const end = 100;
const duration = 500;
let startTime = null;
function step(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = easeOutQuad(progress);
const current = start + (end - start) * eased;
element.style.transform = translateX(${current}px);
if (progress < 1) requestAnimationFrame(step);
}
注意:缓动函数输入必须归一化到 [0, 1],输出也应是 [0, 1],再映射到实际数值范围——这点漏掉会导致动画错位或超限。
真正难的不是让东西动起来,而是让动的过程响应及时、中止干净、适配不同设备帧率,以及在 DOM 大量更新时不让动画被挤掉帧。这些细节不处理,用户第一感觉就是“卡”或“不跟手”。









