在我之前发表的文章CSS 动画的时间统一(Time Uniform For CSS Animation)中,我提到了一种用时间刻度代替keyframes来制作 CSS 动画的方法。由于 CSS 缺乏复杂的数学计算能力,因此适用范围有限。
经过多年的等待,CSS 现在已经支持足够多的数学函数,尤其是 mod(), round(),
和 三角函数。是时候重新审视基于时间的动画方式了,希望这次会更有用。
您可能需要启用 "实验 "功能标志 Experimental feature才能在此页面中查看演示。
基本理念
在着色器程序和其他各种程序中,使用时间来制作动画非常常见。CSS 无法像 JavaScript 那样启动计时器,但现在可以通过 CSS Houdini API 定义一个自定义变量,以毫秒为单位跟踪时间。
每过一毫秒,变量 --t
就会递增 1
,即一秒内递增 1000
。有一个使用 counter() 函数显示变量的技巧。
其他基于 --t
的值也会随之变化。这就是我们获得动画效果的方法。
控制帧频
将更新频率保持在每秒 60 帧(FPS)即可实现流畅的动画效果。浏览器通常会对渲染进行优化,因此更新频率高于 60 帧/秒不会有任何问题。但如果需要,也可以使用 step() 函数手动控制帧频。
变化时间
--t
的值不断向一个方向增长。对于角度值大于 360deg
的情况,这样做没有问题,但并非所有 CSS 属性都将其值视为周期性的。
比方说,我想把一个方框从左到右做成动画,如果平移偏移量与 --t
相关联,它就会不停地增加。
min()
一个预期的结果是,当偏移量达到一个特定值时,它会立即停止。这就是 min() 函数的作用所在。
为了精确控制动画的持续时间,我们可以限制 --t
的值。
mod()
在方框向右移动后,另一个选择是重新开始偏移。现在我们有了 mod() 函数来实现这一功能。
sin()
或者来回移动。
自定义 easing 函数
我们可以使用数学函数和 --t
变量创建自定义的 easing 函数,这可能是 cubic-bezier() 函数无法实现的。
ease-out-cubic
第一步是将 --t
值限定在 0 和 1 之间。
ease-out-elastic
尝试使用 CSS Doodle
随着表达式越来越复杂,var()
和 calc()
往往会降低代码的可读性。因此,我添加了 @t
函数来表示变量--t
。最新版本的 css-doodle 还可以直接在参数内接受简单的数学表达式。
不编写keyframes,代码就很简短。
@grid: 20x1 / 280px 60px;
@gap: 1px;
@size: 100% 20%;
background: #000;
margin: auto;
translate: 0 calc(20px * sin(4*@t(/20, +@i(*6), %360deg)));
它还能快速试验新参数。
@grid: 20x1 / 280px 60px;
@gap: 1px;
@size: 100% 20%;
background: #000;
margin: auto;
translate: 0 calc(20px * sin(3*@t(/50, *@i(*2), %360deg)));
函数 @T and @TS
除了 @t
函数外,大写函数 @T
还表示从一天开始的另一个时间刻度。函数 @TS
是 @t(/1000)
的简写,用于跟踪以秒为单位的时间。
这是一个用 css-doodle 制作的时钟。(CodePen link)
@grid: 4x1 / 240px +.9;
:container {
border-radius: 50%;
border: 4px solid #000;
outline: 10px solid;
outline-offset: 2px;
background:
@doodle(
@grid: 12x1;
@content: @I(- @i(-1));
@place: @plot(r: .8; dir: -90; rotate: -90;);
font-size: 30px;
font-family: sans-serif;
font-weight: bold;
),
@doodle(
@grid: 60x1;
background: #000;
@size: @pn(16px, @m4(8px)) @pn(2px, @m4(1px));
@place: @plot(r: 1;);
);
}
@place: center;
clip-path: polygon(50% -50%, 100% 65%, 0 65%);
background: #000;
@nth(3) {
@size: 4px 90%;
background: red;
rotate: @TS(*6, %360deg);
}
@nth(2) {
@size: 6px 70%;
rotate: @TS(/60, *6, %360deg);
}
@nth(1) {
@size: 10px 45%;
rotate: @TS(/60, /12, *6, %360deg);
}
@nth(4) {
@size: 12px;
border-radius: 50%;
clip-path: none;
background: red;
}
round()
如何让秒针做跳跃运动呢?当然,直接的方法是使用 round() 函数,其中第三个参数指定了四舍五入的间隔。就时钟而言,每一步等于 360 / 60 = 6deg
。
@grid: 4x1 / 240px +.9;
:container {
border-radius: 50%;
border: 4px solid #000;
outline: 10px solid;
outline-offset: 2px;
background:
@doodle(
@grid: 12x1;
@content: @I(- @i(-1));
@place: @plot(r: .8; dir: -90; rotate: -90;);
font-size: 30px;
font-family: sans-serif;
font-weight: bold;
),
@doodle(
@grid: 60x1;
background: #000;
@size: @pn(16px, @m4(8px)) @pn(2px, @m4(1px));
@place: @plot(r: 1;);
);
}
@place: center;
clip-path: polygon(50% -50%, 100% 65%, 0 65%);
background: #000;
@nth(3) {
@size: 4px 90%;
background: red;
rotate: round(down, @TS(*6, %360deg), 6deg);
}
@nth(2) {
@size: 6px 70%;
rotate: @TS(/60, *6, %360deg);
}
@nth(1) {
@size: 10px 45%;
rotate: @TS(/60, /12, *6, %360deg);
}
@nth(4) {
@size: 12px;
border-radius: 50%;
clip-path: none;
background: red;
}
再举一个例子
将颜色和位置一起制作成动画。
(CodePen link).
@grid: 100x1 / 100% auto (4/3) / #10153e;
@size: @rn(1vmin, 5vmin, 10);
background: @p(hsl(@t(/10, +@i(*2), %360), 90%, 80%));
box-shadow: @m5(@r(±23vmin) @r(±23vmin) @r(2vmin) @r(-40px) @p);
translate: @M2(
calc(100px * tan(6*cos(@t(/10, +@i(*10), /6, %360deg))))
);
margin: auto;
border-radius: 50%;
结论
我对这种方法很感兴趣。虽然使用keyframes看起来更直接,但对于一个充满数学计算和输入变量的演示场景来说,使用时间作为变量更有可能获得多样化的结果。