requestAnimationFrame 是动画的正确起点,它按屏幕刷新率自动调度、页面不可见时暂停、协同 GPU 渲染;需配合清屏、状态更新与重绘实现流畅动画。

requestAnimationFrame 是动画的正确起点
直接用 setTimeout 或 setInterval 控制角色移动,容易掉帧、卡顿,尤其在页面切换或后台运行时还会继续执行,浪费资源。浏览器原生的 requestAnimationFrame 才是为动画设计的接口:它按屏幕刷新率(通常是 60Hz)自动调度,页面不可见时自动暂停,且与 CSS 动画、GPU 渲染管线协同更好。
实操建议:
- 每次绘制前调用
requestAnimationFrame,把渲染逻辑写进回调函数里,形成递归调用链 - 不要手动写死 16ms(≈60fps),靠浏览器决定实际间隔;若需降帧(如低配设备),可在回调中加条件跳过部分帧
- 务必在动画循环中清空画布(
ctx.clearRect())或覆盖旧帧,否则会残留拖影
canvas 2D 上画小人并更新位置的关键三步
角色不是“动起来”的,是你每一帧重绘一个位置略有偏移的新图形。核心就是:状态变量 + 清屏 + 重绘。
常见错误现象:ctx.fillRect(x, y, w, h) 画出方块后,x/y 不变,画面就静止;或者忘了 clearRect,结果满屏都是重叠的方块。
立即学习“前端免费学习笔记(深入)”;
示例逻辑片段(简化):
let x = 100, y = 200;
let vx = 2; // 水平速度
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 必须清屏
ctx.fillStyle = 'red';
ctx.fillRect(x, y, 20, 30); // 画小人(暂用矩形代替)
x += vx; // 更新位置
if (x > canvas.width || x < 0) vx = -vx; // 碰墙反弹
requestAnimationFrame(render); // 下一帧
}
用 CSS transform 动 HTML 元素小人?小心布局重排
如果小人是 这类 DOM 元素,用 element.style.transform = 'translateX(100px)' 比改 left/top 高效得多——因为后者触发 layout + paint,前者只走合成层(compositor),不卡主线程。
但要注意:
- 必须给元素设
will-change: transform或先触发硬件加速(如加transform: translateZ(0)),否则首次动画可能闪一下 - 避免在动画中读取
offsetLeft、getBoundingClientRect()等会强制同步 layout 的属性 - DOM 动画适合简单位移/缩放/旋转;复杂形变、逐像素控制(如骨骼、粒子)还是 canvas 更可控
动画卡顿?先查是不是在每帧里做了重计算
很多初学者在 requestAnimationFrame 回调里反复调用 getComputedStyle、遍历大量 DOM、解析 JSON、或执行未优化的碰撞检测——这些操作哪怕只花 3ms,叠加渲染和合成,就超出了 16ms 预算,必然掉帧。
性能关键点:
- 把路径、尺寸、颜色等静态配置提到动画循环外,只在循环内做加减乘除
- 碰撞检测用边界框(AABB)代替像素比对;必要时用空间分割(如四叉树)预筛
- 用
performance.now()包裹关键段,确认单帧耗时;Chrome DevTools 的 Rendering 面板可直观看帧时间
真正难的不是让角色“看起来在动”,而是让它在各种设备、负载、页面状态下都稳定地动。帧循环里多一行没必要的计算,就可能在低端安卓机上从 60fps 掉到 24fps。











