2020年了还不会CSS动画吗

585 阅读9分钟

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%)位置很常用,经常使用关键词fromto代替。下面都是合法的关键帧列表:

/* 例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; }
}

里面有一些需要注意的规定,如:

  • fromto可与百分比混用。

  • 不同时刻的帧可以合并(如果有相同属性),如例168%,72%

  • 某个关键帧中缺省的属性使用插值,如例168%,72%对应的帧都没有top属性,则认为top30%帧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; }
}

厉害,小球学会了瞬移!原因在于:0auto中间不可以计算插值,所以中间帧仍然是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动画迭代次数数字,支持小数,infinite1
animation-fill-mode动画前后元素的样式none, forwards, backwards, bothnone
animation-play-state允许暂停和恢复动画running, pausedrunning

这些属性大都不难理解,即使文字你无法理解,在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动画库即将来袭。

如果你只是需要某种动画,一些支持单个动画代码复制的网站会很合适:

如果想在自己的网站中使用各种动效,那当然选择效果齐全的动效库了,直接看可能是最全的前端动效库汇总,选择强迫症劝退。

#后记

可能有点啰嗦,边学边记,把自己的思路都记了下来。如果有错误,欢迎指正。有帮助的话,评论或点赞鼓励一波,提前谢谢。