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 结束值);
| 关键字 | 等价写法 |
|---|---|
linear | cubic-bezier(0, 0, 1, 1) |
ease(默认) | cubic-bezier(0.25, 0.1, 0.25, 1.0) |
ease-in | cubic-bezier(0.42, 0, 1.0, 1.0) |
ease-out | cubic-bezier(0, 0, 0.58, 1.0) |
ease-in-out | cubic-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;