使用CSS transition和animation改变渐变状态
特别声明,本文根据@ANA TUDOR的《The State of Changing Gradients with CSS Transitions and Animations》一文所整理。
到目前为止,CSS的渐变属性linear-gradient
和radial-gradient
已经是很成熟的CSS特性了,而且repeating-linear-gradient
和conic-gradient
也越来越成熟。CSS渐变特性对于我们的帮助已经非常强大了,它们可以帮助我们绘图、创建图片占位符、制作环形进度条等等。另外还可以通过transition
和animation
让渐变动起来。
但是给渐变添加动画效果目前还有很多极限性,如果不添加额外的元素或其他的渐变属性,有些效果是无法实现的,比如下面这个效果。
不过,在Edge浏览器,使用@keyframes
就可以实现上图的效果,而且代码很简单:
html {
background: linear-gradient(90deg, #f90 0%, #444 0) 50%/ 5em;
animation: blinds 1s ease-in-out infinite alternate;
}
@keyframes blinds {
to {
background-image: linear-gradient(90deg, #f90 100%, #444 0);
}
}
在些基础上,借助CSS的处理器,比如Sass,可以让上面的代码变得更为灵活:
@function blinds($open: 0) {
@return linear-gradient(90deg, #f90 $open*100%, #444 0);
}
html {
background: blinds() 50%/ 5em;
animation: blinds 1s ease-in-out infinite alternate;
}
@keyframes blinds {
to {
background-image: blinds(1)
}
}
虽然上面的代码实现了所需的效果,但使用CSS来维护和使用仍然还是需要编写代码,这是事实。动画效果也只是停留在0%
到100%
之间,能达到我们所要的效果。不过,要是使用0
或0px
来替代0%
的话,结果就会令人失望,动画效果失踪了。更不用说在Chrome和Firefox浏览器上了,能看到的仅仅就是#f90
到#444
两个颜色之间的切换,根本没有停止位置的动效。
庆幸折是,现在我们有一个更好的选择:CSS自定义属性!
虽然我们可以获得过transition
效果(但不是animation
效果),但是如果我们使用的属性是可动画化的,那么CSS自定义属性是不可动画化。比如,当在transfrom
中使用时,我们可以在transition
中使用transfrom
属性。
让我们来做一个效果,复选框选中时,橙色正方形(.box
)将会移动并且会被压扁的效果。我们在.box
中定义了一个自定义属性--f
,并且初始值设置为1
:
.box {
--f: 1;
transform: translate(calc((1 - var(--f)) * 100vw)) scalex(var(--f));
}
当复选框被选中时:checked
,.box
的自定义属性--f
的值变成.5
:
:checked ~ .box {
--f: .5
}
在.box
中添加transition
属性,我们可以让.box
从一个状态到另一个状态时,整个过程是一种细腻的滑动效果。
.box {
--f: 1;
transform: translate(calc((1 - var(--f)) * 100vw)) scalex(var(--f));
transition: transform .3s ease-in;
}
然而,CSS渐变是background-image
,它只在Edge和IE10+中是可动画化的。因此,虽然我们可以让事情变得简单,并减少为transition
生成的CSS代码量(如下面的代码所示),但是在扩展支持方面,一依没有取得任何进展。
.blinds {
background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
transition: .3s ease-in-out;
:checked ~ & {
--pos: 100%;
}
}
值得我们庆幸的是,CSS Houdini的诞生,它允许我们注册自定义属性,然后让它们动起来。不过有点遗憾的是,目前这项强大的特性还只是实验性的特性,如果你想在浏览器中看到相应的效果,需要在实验Web平台特性标志(Experimental Web Platform features)中开启相应的特性。
接着回到我们的示例中来。先注册一个--pos
自定义属性:
CSS.registerProperty({
name: '--pos',
syntax: '<length-percentage>',
initialValue: '0%',
inherits: true
});
注意了。 这里syntax
设置了<length-percentage>
,也就意味着它不仅接受长度和百分比值,还接受它们的calc()
组合。相比之下, <length> | <percentage>
只接受长度和百分比值,而不接受它们的calc()
组合。同时显式的指定了inherits
(继承)是强制性的。
但这样做,在Chrome浏览器下依旧没有任何的效果,这可能是因为,在transition
示例中,transition
是属性,其值依赖于CSS自定义属性,而不是CSS自定义属性本身。众所周知,到目前为止,Chrome浏览器还不支持两个背景图片之间的transition
效果。就这方面而言,Edge显得更为强大,就算是不注册自定义属性--pos
,在Edge可以正常的工作。那是因为在Edge中允许我们在transition
属性中运用渐变属性,从而实现两个渐变之间的过渡效果。
如果在Blink内核浏览器中,开启了实验性Web特性,就可以使用animation
来替代transition
属性。
html {
background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
animation: blinds .85s ease-in-out infinite alternate;
}
@keyframes blinds {
to {
--pos: 100%;
}
}
这样一样,在Edge中又没有效果了,因为到现在为止Edge并不支持CSS自定义属性。因此,需要另外一种方法来处理。这个时候我们应该想起CSS的@supports
,该他出场的时候到了。可以借助@supports
来做一个条件判断:
@function grad($pos: 100%) {
@return linear-gradient(90deg, #f90 $pos, #444 0);
}
html{
background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
animation: blinds .85s ease-in-out infinite alternate;
@supports (-ms-user-select: none) {
background-image: grad(0%);
animation-name: blinds-alt;
}
}
@keyframes blinds-alt {
to {
background-image: grad()
}
}
这种方式并不是唯一的方式。我们可以对渐变的角度做同样的处理。它背后的原理可以说几乎是一样的,只是现在我们的动画不再是一个交替的,不过我们可以在动画中尝试使用easeInOutBack
。
@function grad($ang: 1turn) {
@return linear-gradient($ang, #f90 50%, #444 0);
}
html {
background: grad(var(--ang, 0deg));
animation: rot 2s cubic-bezier(.68, -.57, .26, 1.65) infinite;
@supports (-ms-user-select: none) {
background-image: grad(0turn);
animation-name: rot-alt;
}
}
@keyframes rot {
to {
--ang: 1turn;
}
}
@keyframes rot-alt {
to {
background-image: grad();
}
}
请记住,就像在停止位置的情况一样,我们只能在相同角度单位之间的渐变进行动画,所以在Sass中从grad(0deg)
用grad(0turn)
替代是无法正常工作的。
当然,我们现在使用的CSS自定义属性来接受角度值,而不是长度和百分比:
CSS.registerProperty({
name: '--ang',
syntax: '<angle>',
initialValue: '0deg',
inherits: true
});
以类似的方式,也可以把这个原理运用于径向渐变。
$p: 9%;
html {
--x: #{$p};
--y: #{$p};
background: radial-gradient(circle at var(--x) var(--y), #f90, #444 $p);
}
这里我们注册了两个自定义属性--x
和--y
:
CSS.registerProperty({
name: '--x',
syntax: '<length-percentage>',
initialValue: '0%',
inherits: true
});
CSS.registerProperty({
name: '--y',
syntax: '<length-percentage>',
initialValue: '0%',
inherits: true
});
运用于动画如下:
$p: 9%;
html {
--x: #{$p};
--y: #{$p};
background: radial-gradient(circle at var(--x) var(--y), #f90, #444 $p);
animation: a 0s ease-in-out -2.3s alternate infinite;
animation-name: x, y;
animation-duration: 4.1s, 2.9s;
}
@keyframes x {
to {
--x: #{100% - $p}
}
}
@keyframes y {
to {
--y: #{100% - $p}
}
}
得到的效果如下:
我们可以使用这种技术,在渐变中使用不同的自定义属性,实现相应的动画效果。比如,我们要实现文章开头那种百叶窗的动效,我们可以引入另外两个自定义CSS属性:--c0
和--c1
:
$c: #f90 #444;
html {
--c0: #{nth($c, 1)};
--c1: #{nth($c, 2)};
background: linear-gradient(90deg, var(--c0) var(--pos, 0%), var(--c1) 0) 50%/ 5em;
}
同样的注册需要的自定义属性:
CSS.registerProperty({
name: '--pos',
syntax: '<length-percentage>',
initialValue: '0%',
inherits: true
});
CSS.registerProperty({
name: '--c0',
syntax: '<color>',
initialValue: '#f90',
inherits: true
});
CSS.registerProperty({
name: '--c0',
syntax: '<color>',
initialValue: '#444',
inherits: true
});
对于第一个停止位置,我们使用与之前动画中相同的自定义属性--pos
,除此之外,我们为另外两个自定义属性引入动画的两个steps()
中,每次完成第一个动画的迭代(改变--pos
值):
$t: 1s;
html {
/* 和前面代码相同 */
animation: a 0s infinite;
animation-name: c0, pos, c1;
animation-duration: 2*$t, $t;
animation-timing-function: steps(1), ease-in-out;
}
@keyframes pos {
to {
--pos: 100%;
}
}
@keyframes c0 {
50% {
--c0: #{nth($c, 2)}
}
}
@keyframes c1 {
50% {
--c1: #{nth($c, 1)}
}
}
得到的效果如下:
我们还可以将此应用到radial-gradient()
中:
background: radial-gradient(circle, var(--c0) var(--pos, 0%), var(--c1) 0);
同样也可以运用于conic-gradient()
:
background: conic-gradient(var(--c0) var(--pos, 0%), var(--c1) 0);
如果在重复渐变(repeating-radial-gradient
)中使用的话,可以得到一个类似水波涟漪的效果:
$p: 2em;
html {
/* 和前面代码相同 */
background: repeating-radial-gradient(circle, var(--c0) 0 var(--pos, 0px), var(--c1) 0 $p);
}
@keyframes pos {
90%, 100% {
--pos: #{$p}
}
}
得到的效果如下:
如果运用在重复圆锥属性上,还可以绘制一些螺族或射线的动效:
$p: 5%;
html {
/* 和前面的代码相同 */
background: repeating-conic-gradient(var(--c0) 0 var(--pos, 0%), var(--c1) 0 $p);
}
@keyframes pos {
90%, 100% {
--pos: #{$p}
}
}
我们还可以添加其他的CSS自定义属性,让动效变得更有意思:
$n: 20;
html {
/* 和前面代码相同 */
background: radial-gradient(circle at var(--o, 50% 50%), var(--c0) var(--pos, 0%), var(--c1) 0);
animation: a 0s infinite;
animation-name: c0, o, pos, c1;
animation-duration: 2*$t, $n*$t, $t;
animation-timing-function: steps(1), steps(1), ease-in-out;
}
@keyframes o {
@for $i from 0 to $n {
#{$i*100%/$n} {
--o: #{random(100)*1%} #{random(100)*1%}
}
}
}
同样的方式注册新增加的自定义属性:
CSS.registerProperty({
name: '--o',
syntax: '<length-percentage>+',
initialValue: '50%',
inherits: true
});
效果如下:
我认为使用@keyframes
动画改变渐变的效果看起来很酷。但与此同时,对于跨浏览器的解决方案,JavaScript方案仍然是一个有效的方法。nike air max 1 soccer