控制无限动画:不跳变变速、平滑启停
无限动画(infinite)写起来容易,控制得平滑却不简单:悬停加速时直接改 animation-duration 会让元素瞬间跳位;animation-play-state: paused 停下时则是急停,很生硬。本页介绍 Temani Afif 的两个纯 CSS 技巧,分别解决变速与启停;第一个技巧用到核心属性页讲过的 animation-composition。出处:Part 1 与 Part 2。
1 · 变速不跳变:animation-composition: add
思路:让同一个动画跑两层——第一层一直转,第二层默认 paused;再用 animation-composition: add 把两层的转动叠加。需要加速时,只把第二层的 play-state 设为 running:它从「当前贡献
0」开始往上加,所以位置完全不跳,转速却平滑变成两层之和。
@keyframes spin { to { rotate: 1turn; } }
.box {
animation: spin var(--d) linear infinite,
spin var(--d2) linear infinite;
animation-composition: add;
animation-play-state: running, paused;
}
.box:hover { animation-play-state: running, running; }
关键在最后一行:两层都 running 时转速相加,但第二层的角度从 0 开始累加,所以启用它的瞬间画面不跳变。下面拖动倍率按钮,读数里的累计角度会连续增长、没有断点。
速度关系:启用第二层后角速度 = 1/d + 1/d2,取
就得到 s 倍速。用 abs() / sign() 把 --s 推广到减速 / 停住 / 反向(任意正负值)只需一个变量——完整通用公式见原文 Part 1(用到较新的 CSS
数学函数)。
2 · 平滑启停:用 transition 当「刹车」
play-state: paused/running 是开关式的急起急停。让它渐入渐出的办法:无限动画照常驱动一个属性(这里是 rotate),同时在另一个属性(transform)上挂一段 transition 做反向「刹车」。启动时反向量缓缓归零 →
像慢慢加速;停止时动画冻结、刹车量缓缓回弹 → 像滑行到停。
.box {
animation: spin 2s linear infinite; animation-play-state: paused;
transition: transform 1s ease-out; transform: rotate(0);
}
.box.go {
animation-play-state: running;
transform: rotate(-.25turn);
}
思路是把「速度的突变」用一段反向 transition 抵消掉。原文 Part 2 还给了更通用的写法:在注册的 @property 角度上做加法(rotate 用 calc(var(--a) + var(--b))、--b 走 transition),以及用 @starting-style +
mod() / sign() 让元素停时按最短路径回到初始角度——详见原文 Part 2。
这些技巧用到 animation-composition 以及 abs() / sign() / mod()、注册 @property 等较新能力,以浏览器实现为准;本页两个 demo 只用到其中已较好支持的部分。