掌握css的帧动画

css的key frames知识具备强大的动画能力。 帧动画是这个知识点的一部分,它可以用来实现跳帧动画,就像放动画片一样。

这是一张准备用来演示帧动画的图片:

下面的代码可以让这个图片里面的牛动起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.box {
width: 98px;
height: 103px;
}

.box.move {
background-image: url(./rise_animation_demo.jpg);
background-position: 0 0;
background-repeat: no-repeat;
animation-name: move;
animation-timing-function: steps(6, end);
animation-iteration-count: infinite;
}

@keyframes move {
from {
background-position: 0 0;
}

to {
background-position: -590px 0;
}
}

实际效果及源码可以点击这里查看。

认识帧动画及steps函数

帧动画是设置animation-timing-function生效的,跟普通的keyframes动画不一样的,帧动画的animation-timing-function,设置的不是贝塞尔曲线函数,而是step函数,比如上面的设置的step(6, end)。掌握帧动画的关键就是要掌握step函数的特性。

step函数可以接收2个参数,它的作用:把@keyframes里面定义的相邻的两个帧,划分为step函数第1个参数所指定的阶段数,比如step(6, end),会把相邻的两个帧,划分为6个阶段,每个阶段所对应的动画时长内,应用动画的元素都会保持同一个状态,而不是进行过渡变化。

step函数作用的是相邻的两个keyframe,而不是整个动画过程,虽然上面的例子看起来是影响了整个动画过程,那是因为整个动画确实只定义了两个帧,所以step的作用范围就跟动画完整的范围重合了。这对于掌握帧动画期间每个阶段的持续时长比较关键,假设有如下动画代码定义(随便写的,没实际含义和演示):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.box.move {
animation-name: move;
animation-duration: 1s;
animation-timing-function: steps(6, end);
}

@keyframes move {
0% {
background-position: 0 0;
}

15% {
background-position: -260px 0;
}

100% {
background-position: -590px 0;
}
}

以上代码定义了一个动画,时长为1s,包含了三个keyframe,有2对相邻的keyframe,分别是0~15%,15~100%。 这两段动画过程,占用的动画时长分别是动画完整时长的15%和85%。由于step函数的作用,这两段动画过程又分别被划分为了6个子阶段,每个子阶段的动画持续时长,是当前阶段动画时长的1/6。所以第1对相邻的keyframe,它里面的每个子阶段的最终动画时长就是1/6 * 0.15s;第2对相邻的keyframe,它里面的每个子阶段的最终动画时长就是1/6 * 0.85s。

前面说过“每个阶段所对应的动画时长内,应用动画的元素都会保持同一个状态,而不是进行过渡变化”,steps函数的第二个参数决定了每个阶段应该保持为哪个动画状态。 所谓的动画状态,就是一个表示动画完成进度的百分比值,0%代表元素在动画刚开始的状态,100%代表元素在动画结束时的状态,帧动画的含义就是同一个阶段的动画时长内,动画完成进度的百分比值都是相同的,但是每个阶段的百分比值不同。steps函数与贝塞尔函数的作用是相同的,都是根据动画进程(时间完成进度),得到一个动画完成进度,然后应用到元素上,配合动画完成进度的回调函数来实现动画。

steps函数的第二个参数

steps函数的第二个参数,决定了每个阶段动画完成进度的百分比应该是多少,有如下几个值可用:

start或jump-start(二者是一样的)
end或jump-end(二者是一样的, 这个值是默认值)
jump-none(浏览器暂未支持,本篇不学习)
jump-both(浏览器暂未支持,本篇不学习)

  • step(n, end)
    它的含义是在每个阶段结束时才改变动画状态,如果第二个参数指定为end,那么:
    第1个阶段,动画状态将保持在0%的进度
    第2个阶段,动画状态将保持在1/n的进度

    第m个阶段,动画状态将保持在(m-1)/n的进度(m<=n)

回顾本篇开始的那个例子,我在代码中设置的是steps(6, end):

1
2
animation-timing-function: steps(6, end);
animation-iteration-count: infinite;

6是因为这张图包含了6个状态:

k1至k7分别代表动画进度:0%, 1/6, 2/6, 3/6, 4/6, 5/6, 100%。steps(6, end)让动画分为6个阶段完成,且:
第1个阶段保持为0%的状态;
第2个阶段保持为1/6的状态;
第3个阶段保持为2/6的状态;
第4个阶段保持为3/6的状态;
第5个阶段保持为4/6的状态;
第6个阶段保持为5/6的状态;
第7个阶段,动画进度会变为100%,100%这个状态设置的background-position: -590px;此时图片完全从容器中消失了, 这不是一个正确的动画状态,但是100%这个状态仅仅只会维持一丁点的时间,因为此时当前这一轮动画已经运行结束;我们设置了动画执行次数为infinite,下一轮动画又会从头开始执行,最终表现出来的动画效果就是符合预期的。

为了验证第7个阶段的状态,可以把动画代码调整一下:

1
2
animation-iteration-count: 1;
animation-fill-mode: forwards;

然后重新去运行代码,会看到元素在动画结束后始终保持在背景图片为空白的状态(background-position: -590px)。

  • step(n, start)
    它的含义是在每个阶段开始时就改变动画状态,如果第二个参数指定为start,那么:
    第1个阶段,动画状态将保持在1/n的进度
    第2个阶段,动画状态将保持在2/n的进度

    第m个阶段,动画状态将保持在(m)/n的进度(m<=n)

还以上面的例子和图来说明:

k1至k7分别代表动画进度:0%, 1/6, 2/6, 3/6, 4/6, 5/6, 100%。 如果设置的是steps(6, start),那么:
第1个阶段保持为1/6状态;
第2个阶段保持为2/6的状态;
第3个阶段保持为3/6的状态;
第4个阶段保持为4/6的状态;
第5个阶段保持为5/6的状态;
第6个阶段保持为6/6的状态;
这个设置会丢失掉动画0%的这个状态,也就是上面那种图里面的第一帧图,同时最后一个阶段保持为100%的状态,是一个空白状态,最后动画表现出来的效果就是不符合预期的。

更加形象地来观察start或end的效果

请点击这个例子查看start、end以及ease的对比效果。该页面内还可更改animation-direction来观察不同的timingFunction的特性。

本篇内容参考自css标准文档: https://www.w3.org/TR/css-easing-1/#step-easing-functions