JS 动画函数与渲染性能
如何写一个通用的 JS 动画函数?
- 三分职责:
duration(总时长) +timing(timeFraction)(时间比例 → 完成度) +draw(progress)(把完成度画出来); - 时间比例:
timeFraction = (now − start) / duration, 上截断到 1; - 缓动语义:
timing吃 0..1、吐完成度, 与 CSS 的transition-timing-function同义;
function animate({ timing, draw, duration }) {
const start = performance.now();
requestAnimationFrame(function frame(time) {
const timeFraction = Math.min((time - start) / duration, 1);
draw(timing(timeFraction)); // 画当前帧
if (timeFraction < 1) requestAnimationFrame(frame);
});
}
animate({
duration: 1000,
timing: (t) => t,
draw: (p) => {
elem.style.width = p * 100 + "%";
},
});
- 自由度:
timing不受贝塞尔曲线限制,draw可改任意属性、建新元素、画 canvas;
常用缓动函数有哪些?
| 名字 | 实现 | 效果 |
|---|---|---|
| 幂次 | t ** n | n 越大加速越猛(n=2 抛物线) |
| 圆弧 | 1 - Math.sin(Math.acos(t)) | 四分之一圆式加速 |
| back | t² * ((x + 1) * t - x) | 拉弓: 先回缩再射出, x 为弹性系数 |
| bounce | 分段抛物线 | 落地弹跳, 开头就"弹" |
| elastic | 2 ** (10 * (t - 1)) * Math.cos(20 * Math.PI * x / 3 * t) | 弹簧振荡, x 为初始幅度 |
const makeEaseOut = (timing) => (t) => 1 - timing(1 - t);
const makeEaseInOut = (timing) => (t) =>
t < 0.5 ? timing(2 * t) / 2 : (2 - timing(2 * (1 - t))) / 2;
- 语义: 反向播放用
timingEaseOut(t) = 1 - timing(1 - t)把开头效果搬到结尾, 首尾同效则用easeInOut; - 例子:
makeEaseOut(bounce)把"落地弹跳"变成"先冲到顶, 再在顶部弹";
为什么优先动画 transform 和 opacity?
- 渲染三步: Layout(算几何与位置) → Paint(算外观与颜色) → Composite(合成像素并应用 transform), 动画期间每帧重复;
- 属性差异: 不改几何的属性(如
color)可跳过 Layout; 更少的属性直接进 Composite, 各属性触发哪几步见 csstriggers.com; transform: 作用于元素整体、不影响邻居, 在 Composite 阶段叠加并可用 GPU 加速, 完全跳过 Layout 与 Paint;opacity: 同样不触发 Layout(Mozilla Gecko 下连 Paint 也跳过), 适合淡入淡出;- 实践: 用
transform: translateX()代替left/margin-left, 用transform: scale()代替改宽高;
#boat {
transition:
transform 2s ease-in-out,
opacity 2s ease-in-out;
}
.move {
transform: translateX(300px);
opacity: 0;
}