跳到主要内容

CSS 动画与缓动

贝塞尔曲线由什么决定?​

  • 决定因素: 控制点, 2 个以上均可;
  • 阶数: 阶数 = 控制点数 − 1; 2 点线性, 3 点二次(抛物线), 4 点三次;
  • 位置: 首末控制点在曲线上, 中间控制点一般不在;
  • 凸包: 曲线恒在控制点凸包内, 故可先用凸包相交测试快速排除;
  • 价值: 拖点即可直观改形, 多条曲线拼接能画出任意图形(字体、矢量图);

De Casteljau 算法(4 点为例): 连接 1->2->3->4; 对每个 t∈[0,1], 在各线段按比例 t 取点并相连(N 点得 N−1 条线, 反复直到剩 1 点); 最后那条线段上按比例 t 取的点即曲线上的点;

  • 数学等价: P = (1−t)³P₁ + 3(1−t)²tP₂ + 3(1−t)t²P₃ + t³P₄, 把 x、y 分别代入即可;

cubic-bezier 与 steps() 怎么描述时间分布?​

  • 输入约束: 只写第 2、3 个控制点, 首点固定 (0,0), 末点固定 (1,1);
  • 写法: cubic-bezier(x2, y2, x3, y3); 中间点 x 必须在 0..1, y 可任意;
  • 轴含义: x 是时间(到 1 即 duration 用完), y 是完成度(0 起始值, 1 结束值);
关键字等价写法
linearcubic-bezier(0, 0, 1, 1)
ease(默认)cubic-bezier(0.25, 0.1, 0.25, 1.0)
ease-incubic-bezier(0.42, 0, 1.0, 1.0)
ease-outcubic-bezier(0, 0, 0.58, 1.0)
ease-in-outcubic-bezier(0.42, 0, 0.58, 1.0)
  • 超出范围: y 为负或大于 1 时, 属性会先越过起始值、再冲过结束值, 最后回位;
  • 逐帧: steps(步数[, start|end]) 把时间与取值等分成若干步; start 立刻迈第一步, end 每步到区间末尾才生效, 如 transition: transform 9s steps(9, start) 每 1s 跳 10%;
  • 简写: step-start = steps(1, start), step-end = steps(1, end), 等同瞬间切换;
  • 选曲线: 用 cubic-bezier.com 或开发者工具 Styles 面板中 cubic-bezier 前的图标拖拽;

transition 由哪几个属性构成?​

  • 思路: 只声明"哪个属性、花多久", 属性一变浏览器自动补间;
  • 四属性: transition-property(属性名或 all)、transition-duration(s/ms)、transition-timing-function、transition-delay;
  • 简写: transition: property duration timing-function delay, 逗号分隔并列多组;
  • 可动画性: 部分属性不可动画, 但常用数值型属性基本都可;
  • 负延迟: -1s 配时长 2s → 立即从动画中点起跑, 总时长 1s, 用于从任意中间态接入;
  • 触发: 加类名或改行内样式都可;
.animated {
transition:
font-size 3s,
color 2s;
}
/* el.classList.add('animate') 或改 font-size 行内样式即触发 */

@keyframes 与 animation 怎么用?​

  • @keyframes: 为动画命名并声明关键帧, 用 from/to 或百分比定位;
@keyframes go-left-right {
from {
left: 0px;
}
to {
left: calc(100% - 50px);
}
}
.progress {
animation: go-left-right 3s infinite alternate;
}
  • 简写顺序: name duration timing-function delay iteration-count direction fill-mode;
  • 关键字: infinite 无限循环, alternate 每次反向播放;
  • 填充模式: fill-mode 决定动画时间之外是否保留首帧/末帧样式, 是简写最后一位;
  • 取舍: @keyframes 适合"页面上总有东西在动", 常规单次补间用 transition 更省事;

动画结束后如何执行代码?​

  • transitionend: 过渡结束时触发, 常用于串联下一段或翻转方向;
  • 事件对象: event.propertyName 是刚动画完的属性(并列多组时用来过滤), event.elapsedTime 是不含 transition-delay 的实际秒数;
  • 触发粒度: 一次只对应一个属性;
  • 关键帧动画: 对应 animationend; JS 动画没有事件, 在自己的帧回调里收尾;
boat.addEventListener("transitionend", () => go()); // 结束后接上下一段

CSS 动画和 JS 动画怎么取舍?​

维度CSS 动画JS 动画
上手简单事简单做需自己写调度与缓动
性能轻量, 浏览器统一调度取决于实现, 按帧驱动
缓动仅贝塞尔与 steps任意函数
能力只能改属性复杂路径、canvas 绘制、新建元素
收尾transitionend自行在回调中处理
  • 结论: 绝大多数动画交给 CSS, 复杂路径、非贝塞尔缓动、canvas 动画交给 JS;