跳到主要内容

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 ** nn 越大加速越猛(n=2 抛物线)
圆弧1 - Math.sin(Math.acos(t))四分之一圆式加速
backt² * ((x + 1) * t - x)拉弓: 先回缩再射出, x 为弹性系数
bounce分段抛物线落地弹跳, 开头就"弹"
elastic2 ** (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;
}