《CSS揭秘》读书笔记11

缓动效果

这一节主要讲的就是timing-function这个css3动画的动画速度属性的cubic-bezier()的这个函数,这个函数的四个值自己思考出来的还是不精确,我之前都是一点点改里面的数值然后手动运行看效果。书上讲了一堆我已经知道的,但是具体里面这几个值,还是要自己依据效果去调咯~~

逐帧动画

如果我们需要的不是平滑的过渡动画,而是需要动画以逐帧动画的形式来展现,比如我们想播放一个合成为精灵图的动画,那就需要每张图片之间硬切,不能够滚动。css提供了一个steps()函数,来实现以上这种效果。

与贝塞尔曲线调速函数迥然不同的是,steps()会根据你指定的步进数量,把整个动画切分为多帧,而且整个动画会在帧与帧之间硬切,不会做任何插值处理。

1
2
3
{
animation: loader 1s infinite steps(8);
}

别忘了 steps() 还接受可选的第二个参数,其值可以是 start 或 end(默认值)。这个参数用于指定动画在每个循环周期的什么位置发生帧的切换动作,但实际上这个参数用得并不多。如果我们只需要一个单步切换效果,还可以使用 step-start 和 step-end 这样的简写属性,它们分别等同于 steps(1, start) 和 steps(1, end)。

想了想,除了播放精灵图,好像也没啥其他用处了,除了设计目的就是为了模拟这种卡顿播放的效果。

闪烁效果

第一种解决方案是最普通的:

1
2
3
4
5
@keyframes blink-smooth { 50% { color: transparent } }
.highlight {
animation: 1s blink-smooth linear 3;
}

我在书中原文基础上加入了linear使得动画呈现为线性。

第二种解决方法是利用animation-direction

animation-direction的唯一作用就是反转每一个循环周期(reverse),或第偶数个循环周期(alternate),或第奇数个循环周期(alternate-reverse)。它的伟大之处在于,它会同时反转调整函数,从而产生更加逼真的动画效果。

示意图和代码如下:

animation-direction.png

1
2
3
4
5
@keyframes blink-smooth { to { color: transparent } }
.highlight {
animation: .5s blink-smooth 6 alternate;
}

我们必须把动画循环的次数翻倍(而不是像前面的方法那样把循环周期的时间长度翻倍),因为现在一次淡入淡出的过程是由两个循环周期组成的。基于同样的原因,我们也要把animation-duration减半。

如果想做到不平滑、直接切换的闪烁效果,则需要以下代码:

1
2
3
4
5
@keyframes blink { 50% { color: transparent } }
.highlight {
animation: 1s blink 3 steps(1); /* 或用step-end */
}

从此我们可以看出,steps()是将animation中每个阶段都切分播放,假如动画有 2 个阶段,steps(4)会将动画切割为 8 段播出。

打字动画

书中的解决方案是改变元素的宽度,将字母逐字露出,达到打字动画的效果。但是这个方案要求内容等宽,知道内容字符长度,且没有与 js 分离。那何不直接用 js 来实现呢?线上的一些解决方案都是用 js 来实现的。

状态平滑的动画

假如我们想要让动画在与用户交互的时候播放,例如:hover,那么如果用户在动画未播放完的时候移开鼠标,动画会立即停止播放并生硬地跳回开始状态。如何改善这一情况呢?

为了修复这个问题,我们需要换个角度来思考:我们在这里到底想要实现什么样的结果。我们需要的并不是在:hover时应用一个动画,因为这意味着动画被中断时的状态是无处保存的。我们需要的是当失去:hover状态时暂停动画。幸运的是,有一个属性正好是为暂停动画的需求专门设计的:animation-play-state

1
2
3
4
5
6
7
8
9
10
11
12
13
@keyframes panoramic {
to { background-position: 100% 0; }
}
.panoramic {
width: 150px; height: 150px;
background: url("img/naxos-greece.jpg");
background-size: auto 100%;
animation: panoramic 10s linear infinite alternate;
animation-play-state: paused;
}
.panoramic:hover, .panoramic:focus {
animation-play-state: running;
}

我们把动画加在.panoramic这条样式中,但是让它一开始就处于暂停状态,直到:hover时再启动动画。这再也不是添加和取消动画的问题了,而只是暂停和继续一个一直存在的动画,因此再也不会有生硬的跳回现象了。

沿环形路径平移的动画

我们想实现一个效果,一个图片在一个圆形路径上移动,但是本身的方向一直向上。我们的第一反应是用一个元素包裹图片,包裹元素围绕圆心旋转运动,图片本身做等速相反的旋转来抵消方向上的偏差。

1
2
3
4
5
6
<!-- html -->
<div class="path">
<div class="avatar">
<img src="lea.jpg" />
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
/* css */
@keyframes spin {
to { transform: rotate(1turn); }
}
.avatar {
animation: spin 3s infinite linear;
transform-origin: 50% 150px; /* 150px = 路径的半径 */
}
.avatar > img {
animation: inherit;
animation-direction: reverse;
}

我们利用在闪烁那节中了解过的animation-direction避免了建立两套相似的动画。

如果要求我们只用一个元素来完成这个效果呢?

“transform-origin 只是一个语法糖而已。实际上你总是可以用translate() 来代替它。”
——Aryeh Gregor

他确实一语道破天机:每个transform-origin都是可以被两个translate()模拟出来的。比如,下面两段代码实际上是等效的:

1
2
3
4
5
6
7
8
9
10
{
/* 1 */
transform: rotate(30deg);
transform-origin: 200px 300px;
/* 2 */
transform: translate(200px, 300px)
rotate(30deg)
translate(-200px, -300px);
transform-origin: 0 0;
}

transform-origin也只是把坐标系统进行了位移而已。所以我们可以对代码做出以下改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@keyframes spin {
from {
transform: translate(50%, 150px)
rotate(0turn)
translate(-50%, -150px)
translate(50%,50%)
rotate(1turn)
translate(-50%,-50%)
}
to {
transform: translate(50%, 150px)
rotate(1turn)
translate(-50%, -150px)
translate(50%,50%)
rotate(0turn)
translate(-50%, -50%);
}
}
.avatar { animation: spin 3s infinite linear; }

然后我们可以把把连续的translate()变形操作合并起来,此外,由于同一关键帧内的两次旋转也会相互抵消,我们还可以把旋转之前和之后的水平位移动作去掉,再把垂直位移合并起来。这样一来就得到了如下的关键帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@keyframes spin {
from {
transform: translateY(150px) translateY(-50%)
rotate(0turn)
translateY(-150px) translateY(50%)
rotate(1turn);
}
to {
transform: translateY(150px) translateY(-50%)
rotate(1turn)
translateY(-150px) translateY(50%)
rotate(0turn);
}
}
.avatar { animation: spin 3s infinite linear; }

实测有效!书中虽然说这个那个的合并,但我到底也没明白这个translate()到底是怎么就连续了(●’◡’●)