CSS动画
0、前言
个人学习CSS动画的整理。好像没有什么浅显易懂的CSS动画教程,就写了一下。适合不熟悉CSS动画的读者。
1、从一个简单动画开始
CSS动画是通过@keyframes声明的关键帧和animation相关的动画属性实现的,因为属性太多,直接看太枯燥,也容易一头雾水,现在,通过一个简单的例子说明一下。
下面是一个滑动的小球:
<div class="container">
<div class="ball"></div>
</div>
.container {
width: 300px;
height: 100px;
border: darkgray solid 2px;
}
.ball {
width: 100px;
height: 100px;
background-color: #4a9ef1;
border-radius: 50%;
animation: move 3s 1s infinite alternate;
}
@keyframes move {
from { margin-left: 0; }
to { margin-left: 200px; }
}
现在,开始分析上面的动画。上面CSS中,忽略基本的样式,看今天的两个主角@keyframes关键字和animation属性,结合动画来说明这两个。
我们都清楚,动画是通过一帧一帧的图片快速切换得到的,CSS动画也是如此,比如:0ms时,margin-left: 0px; 1ms时,margin-left: 1px; 2ms时,margin-left: 3ms; ...... 200ms时,margin-left: 200px; 这些样式组合就变成了一个从左到右滑动的动画。幸运的是,在CSS中我们并不需要明确给出每个时刻的样式,事实上,在从左到右的滑动中,有两个关键的时刻,起始时刻和终止时刻,这两个时刻的样式在CSS中称为关键帧(keyframe),在一个动画中,我们只需要给出关键帧,CSS会根据关键帧之间的变化,计算补上中间的变化帧。不难理解,上面的@keyframes指定了关键帧,并且关键帧直观上理解就是from某个样式to另一个样式,很容易懂。注意到@keyframes后的名称move表示了这个关键帧列表的名称,它在后面的动画中使用。
接着看动画如何被应用到对应的元素上。animation有很长的一串,看起来确实很费劲,它是很多属性的简写属性,在对各个子属性有详细了解前,还不能正确理解它的含义。不过,可以肯定的是,它使用了move作为动画关键帧列表。
至此,尽管我们还不了解具体的语法,但是对动画的工作原理已经有了大体的认识。我们可以有个简单的理解:@keyframes定义了动画的一组关键帧,元素可以使用animation属性使用由关键帧变化形成的动画。这种关系就像声明变量和使用变量。
2、正式学习
2.1、使用@keyframes声明关键帧列表
声明CSS动画中的关键帧使用@keyframes关键词,它的语法如下:
@keyframes <keyframes-name> {
/*keyframe-block-list*/
<percentage>{/*...*/}
<percentage>{/*...*/}
<percentage>{/*...*/}
/*...*/
}
里面由一组关键帧组成,帧数根据动画的内容决定。百分数表示该帧对应的进度,因为起始(0%)和终止(100%)位置很常用,经常使用关键词from和to代替。下面都是合法的关键帧列表:
/* 例1 */
@keyframes identifier {
0% { top: 0; left: 0; }
30% { top: 50px; }
68%, 72% { left: 50px; }
100%{ top: 90px; } /*无效,被后面覆盖*/
100% { top: 100px; left: 100%; }
}
/* 例2 */
@keyframes important1 {
from { margin-top: 50px; }
50% { margin-top: 100px; }
to { margin-top: 150px; }
}
里面有一些需要注意的规定,如:
-
from与to可与百分比混用。 -
不同时刻的帧可以合并(如果有相同属性),如
例1中68%,72%。 -
某个关键帧中缺省的属性使用插值,如
例1中68%,72%对应的帧都没有top属性,则认为top是30%帧到100%帧均匀变化。不支持插值的属性会被忽略。 -
同一帧中重复定义的属性取最后面的值。
-
!important被忽略。
其中最重要的是第3条,很多初学者不明白其中的含义,容易写成错误的关键帧,导致动画异常。以开头滑动的小球的例子来说:
@keyframes move {
from { margin-left: 0; }
to { margin-left: 200px; }
}
这里用margin-left: 200px;}来表示终止时刻的样式,限制了它的宽度,聪明的同学可能想到写成:
@keyframes move {
from { margin-left: 0; }
to { margin-right: 0; }
}
这种方式看似更灵活,因为不论外容器多大,总是可以从左到右移动。但很遗憾,这无法按你想象的工作。你可以在CodePen里修改看看变化,并且我建议你下面都这样做。
让我们看看到底发生了什么。根据第3条规则,缺省的属性按插值计算。上面的代码相当于:
@keyframes move {
from { margin-left: 0; }
to { margin-left:0; margin-right: 0; }
}
100%帧中的margin-right属性因为冲突不起作用,所以整过过程左外边距都是0。这时,更聪明的同学灵光一闪,活用auto:
@keyframes move {
from { margin-left: 0; }
to { margin-left:auto; margin-right: 0; }
}
厉害,小球学会了瞬移!原因在于:0与auto中间不可以计算插值,所以中间帧仍然是margin-left:0;,而到了终止帧突然跳到右边。
难道没有什么完美的解决方案吗?——有的。更更聪明的同学可能想到了,利用calc()函数:
@keyframes move {
from { margin-left: 0; }
to { margin-left: calc(100% - 100px); }
}
注意减号两边都有空格。它终于能正常工作了。
总结一下上面的采坑,得出一个规律:**在一组关键帧中,尽量使用相同的属性。**即使可以省略,但是明确写出来可以让你很清楚个个帧之间的变化。
2.2、使用animation属性
CSS使用animation属性为元素添加动画。animation(MDN文档)是多个属性的简写,其子属性有:
| 属性名称 | 含义 | 可取值 | 默认值 |
|---|---|---|---|
| animation-name | 动画关键帧列表名称 | 标识符 | none |
| animation-duration | 动画时长 | 时间 | 0s |
| animation-timing-function | 时间计算函数 | 时间函数 | ease |
| animation-delay | 从样式应用到动画启动的时延 | 时间 | 0s |
| animation-direction | 动画的运行方向 | normal, reverse, alternate, alternate-reverse | normal |
| animation-iteration-count | 动画迭代次数 | 数字,支持小数,infinite | 1 |
| animation-fill-mode | 动画前后元素的样式 | none, forwards, backwards, both | none |
| animation-play-state | 允许暂停和恢复动画 | running, paused | running |
这些属性大都不难理解,即使文字你无法理解,在CSS中实际试一试就知道了。
不要被这么多的子属性吓到,实践中我们大多时候直接使用animation简写,所以你没必要记住每一个名称。得益于默认值的存在,我们往往不需要给出所有的子属性。最简单的:
animation: <animation-name> <animation-duration>;
就可以应用动画了。注意animation-duration默认为0,所以必须手动设置。而其它的属性,只有在需要的时候才指明它们。
- 如果希望动画延迟开始,设置
animation-delay。此时animation属性值中的第一个时间值为animation-duration,第二个为animation-delay。 - 如果希望动画多次执行,设置
animation-iteration-count。 - 如果希望控制动画的速度,设置
animation-timing-function。你可能发现前面小球不是匀速滑动的, 那是因为使用了默认ease时间函数。linear时间函数可以实现均匀变化。更多时间函数。 - 如果希望控制动画的方向,设置
animation-direction。 - 如果希望控制元素运行动画后静止的样式,设置
animation-fill-mode。 animation-play-state更多的是提供JS控制接口。
建议这里一一尝试,去体会各个属性对动画整体的影响。
到这里,你就能尝试去创建各种动画了。
2.3、多个动画
可以在一个元素上应用多个动画,多个动画之间叠加运行,而不是串接运行。比如,在小球上应用左右移动的动画和上下移动的动画,结果是小球斜向运动。
.container {
width: 300px;
height: 300px;
border: darkgray solid 2px;
}
.ball {
width: 100px;
height: 100px;
background-color: #4a9ef1;
border-radius: 50%;
/*应用多个动画*/
animation: move 2s linear infinite alternate,
updown 2s linear infinite alternate;
}
@keyframes move {
from {
margin-left: 0;
}
to {
margin-left: 200px;
}
}
@keyframes updown {
from {
margin-top: 0;
}
to {
margin-top: 200px;
}
}
2.4、使用动画需要关注的问题:性能
详细内容请参看High Performance Animations(别慌,中文的)。
总结一下就是,当动画内容影响到布局时,会导致浏览器页面的重排布,影响性能。建议不要为了不必要的动画影响性能,但是4个安全属性可以放心使用:
现在,让我们使用上面的属性来优化一下一开始的案例,并为小球添加滚动效果:
代码:
<div class="container">
<div class="ball">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
.container {
width: 800px;
height: 100px;
border: 5px #acefbd solid;
}
.ball > div {
width: 50%;
height: 50%;
}
.ball > div:nth-child(1) {
background-color: #f8df70;
}
.ball > div:nth-child(2) {
background-color: #5bae23;
}
.ball > div:nth-child(3) {
background-color: #63bbd0;
}
.ball > div:nth-child(4) {
background-color: #80766e;
}
.ball {
width: 100px;
height: 100px;
border-radius: 50%;
display: flex;
flex-wrap: wrap;
overflow: hidden;
animation: slid 4s linear alternate infinite;
}
@keyframes slid {
from {
transform: translateX(0) rotate(0);
}
to {
transform: translateX(700px) rotate(720deg);
}
}
2.5、其它内容
3、动画事件(补充)
三个动画事件:
animationstart:动画启动前触发。animationend: 动画结束后触发。animationiteration: 中间的动画周期触发。
监听某个元素上的动画事件代码示例(来自MDN):
var e = document.getElementById("watchme");
e.addEventListener("animationstart", listener, false);
e.addEventListener("animationend", listener, false);
e.addEventListener("animationiteration", listener, false);
e.className = "slidein";
尤其需要注意最后一行,为了成功监听到animationstart事件,我们总是在JS中手动添加动画类。
4、真正厉害的人都是不写动画的(必看)
我一个码农,你以为我是不会写动画吗?其实我是不会创作动画,难道注定要放弃高大上的CSS动画吗?这个时候,鲁迅先生的拿来主义就派上用场了。听过动画库吗?就是别人做好的那种,我不会写,还不会CV吗?
收藏夹准备好,一波CSS动画库即将来袭。
如果你只是需要某种动画,一些支持单个动画代码复制的网站会很合适:
如果想在自己的网站中使用各种动效,那当然选择效果齐全的动效库了,直接看可能是最全的前端动效库汇总,选择强迫症劝退。
#后记
可能有点啰嗦,边学边记,把自己的思路都记了下来。如果有错误,欢迎指正。有帮助的话,评论或点赞鼓励一波,提前谢谢。