十分钟看完文章还学不会animation,你骂我!

446 阅读12分钟

前言

作者最近在做一个需求时,需要自己编写一个简单的loading动画,虽然最终完成了,但过程中遇到了不少困难。因此,作者决定重新系统地学习一遍关于CSS animation 的使用。本文将以实际需求中的目标效果为导向,进行内容的撰写与阐述。说人话就是先举例子,通过一步一步实现例子,做到对animation 的熟悉。

前置知识

@keyframes

关键帧 @keyframes通过定义动画序列中的关键帧样式,来控制动画序列的中间步骤。也就是说:通过 @keyframes规则,可以定义出一段动画中几个中间节点的样式,节点与节点之间通过补间动画进行过渡。

231856fa4f3149f88b2a8bcf2d80618d.gif

这种手翻书的每一页都可以看作是一个关键帧,动画序列中的一个节点。css中的关键帧不会这么密集,而是通过动画的方式从一个关键帧过度到另一个关键帧。

一个最简单的关键帧规则定义:

@keyframes expansion {
  from {
    width:200px;
  }

  to {
    width:300px;
  }
}

expansion为关键帧规则的名称,用于与animation-name属性相匹配。 fromto代表着动画开始/结束的关键帧节点。fromto也可以用0%100%来表示,这也说明了我们可以使用百分比来定义整段动画中的任意时刻的关键帧样式,比如50%就是表示动画进行到一半时刻的关键帧。

上述代码可以解释为:名称为expansion的关键帧规则,定义了动画开始和结束状态两个关键帧节点,开始时width:200px;,动画结束的时候width:300px;,两个阶段如何过渡,就不是关键帧规则所规定了,而是animation相关属性了。

animation-name

animation-nameCSS属性用于指定一个或者多个关键帧规则。

.box {
    width:200px;
    height:200px;
    background-color:#000;
    animation-name:expansion;
}

@keyframes expansion {
  from {
    width:200px;
  }

  to {
    width:300px;
  }
}

animation-name与关键帧keyframes结合起来定义了动画的样式变化。上述代码的意思就是类名为box的元素,从动画运行开始时刻是width:200px,动画结束时刻width:300px。关键帧是静态的,而动画是动态的,我们还需要确认动画要运行多长时间。

animation-duration

animation-durationCSS属性设置动画完成一个动画周期所需要的时间。一个动画周期指的就是我们关键帧keyframes中从0%100%的过程。

.box {
    width:200px;
    height:200px;
    background-color:#000;
    animation-name:expansion;
    animation-duration:3s;
}

@keyframes expansion {
  from {
    width:200px;
  }

  to {
    width:300px;
  }
}

animation-duration定义的时间可以用秒(s)或毫秒(ms)指定。上述代码的意思就是类名为box的元素,使用了名为expansion的关键帧规则,并且一个动画周期的时间定义为3秒。

animation-duration:1s;

1s则代表动画运行一秒。

animation-duration:100ms;

100ms,则表示动画运行100毫秒。

让方块动起来

接下来上述的CSS属性来实现一下让方块动起来的效果。

<style>
    .container {
      width: 1000px;
      height: 1000px;
      position: relative;
      border: 1px solid #000;
    }
    .box {
      width:200px;
      height:200px;
      background-color:#000;
      position: absolute;
      animation-name: expansion;
      animation-duration: 3s;
    }
    @keyframes expansion {
      from {
          left: 0;
      }

      to {
        left: 500px;
      }
    }
</style>
<body>
  <div class="container">
    <div class="box"></div>
  </div>
</body>
  1. 我们定义了keyframes关键帧,设置动画开始的时候left为0,动画结束的时候left500px
  2. 通过animation-name与关键帧进行匹配,定义了动画如何运行。
  3. 通过animation-duration设置了动画运行的时间。

这样一个最简单的动画就实现了,下面让我们看看效果:

动画.gif

但是动画运行结束之后会恢复到元素的初始位置,这样看起来会非常的奇怪,在日常需求中想要的效果通常由两种: 1. 动画结束之后,元素保持不动 2. 动画循环反复的播放

这就涉及到animation的另一个属性了。

animation-direction

animation-directionCSS属性定义了动画正向播放、反向播放还是正向与反向交替播放。 animation-direction的值可以是normalreversealternatealternate-reverse

  1. normal:动画在每个循环中正向播放。
  2. reverse:动画在每个循环中反向播放。
  3. alternate:动画在每个循环中正反交替播放,第一次迭代是正向播放。
  4. alternate-reverse:动画在每个循环中正反交替播放,第一次迭代是反向播放。

上述例子中,动画运行结束之后方块会恢复到初始位置,就是因为animation-directionCSS属性的默认值是 normal,动画正向播放,播放结束后,是要恢复到元素的原本样式的,所以方块的位置会置为初始位置。

设置为reverse的效果如下:

reverse.gif

动画反向播放,方块从结束位置运行到初始位置,然后保持不动,这是符合我们的预期的。元素的原本样式就是left:0,动画反向播放结束时刻方块的位置也是left:0,正好是本身的初始位置,因此不会像值为normal一样突变到初始位置。

但是当animation-direction设置alternate或者alternate-reverse的时候,动画效果和normalreverse一模一样,没有所谓的正反交替播放。这是为什么呢?其实正如animation-direction属性名描述的一样,这个CSS属性只是定义了动画运行的方向。想要动画出现正反交替播放(正向播放一次以上,反向播放一次以上),那就需要动画播放多次,才能触发效果。animation-iteration-count属性则是定义动画播放次数的。

animation-iteration-count

animation-iteration-countCSS属性设置动画序列在停止前应播放的次数。换句话说就是定义动画播放几次。 animation-iteration-count的值可以设置为:

  1. number数字,如1,2,3,4,1000...
  2. infinite,表示无限循环播放动画。 animation-iteration-count的默认值是1,这也就解释了为什么animation-direction设置为 alternatealternate-reverse的时候,展示的效果是normalreverse了,因为只运行一次就结束了,只会出现它们的首次动画播放方向,也就是对应的normalreverse

animation-iteration-count的值是可以设置为非整数,如设置为1.5,那么动画就会运行1.5次,0.5次就是动画运行一半。

animation-iteration-count的值设置为infiniteanimation-direction设置为 alternate的效果:

reverse.gif

可以看到动画循环反复播放,一个常见的动画效果实现了。

我们通过gif图片看到,方块运行是有加速和减速的过程的,这种运行效果可能不是我们想要的,我们可能想要匀速播放,或者自定义播放的加速度。下面要讲的animation-timing-function就是设置这种运行效果的。

animation-timing-function

animation-timing-functionCSS属性设置动画在每个周期的持续时间内如何进行。

animation-timing-function的值可以设置为:

  1. 非阶跃(non-step)关键字值(如 ease、linear、ease-in-out 等)。
  2. 开发者自定义的三次贝塞尔曲线,其中 p1 和 p3 的值必须在 0 到 1 的范围内。
  3. steps(n, <jumpterm>),使用场景是无需关键帧之间的补间动画,直接进行跳跃。

animation-timing-function的默认值时easeease的解释就是:动画在中间加速,在结束时减速。这也就是我们看到的动画开始和结束慢,中间速度快的效果。 如果想要匀速运行动画效果的话,可以设置值为linear,效果如下:

linear.gif

非阶跃关键字值包括:ease,linear,ease-in,ease-out,ease-in-out,它们都分别代表着比较常见的速度效果(可以查看MDN中的文档来了解它们各自代表的效果)。

这里要特别提一下easeease-in-out的区别。这两个关键字的效果都是动画开始和结束速度较慢,在中间加速。二者的区别就是ease中间加速度较快,速度变化较大,ease-in-out加速度较慢,速度变化较小,速度更加均匀。

如果非阶跃关键字无法满足需求,也可以通过自定义三次贝塞尔曲线,来描绘动画的运行速度效果。 在这个网站里可以自定义三次贝塞尔曲线并获取到相关的值:cubic-bezier.com/。

animation-delay

animation-delayCSS属性指定从应用动画到元素开始执行动画之前等待的时间量。动画可以稍后开始、立即从开头开始或立即开始并在动画中途播放。

animation-delay的值可以设置为s或者ms,且可以是正数,也可以是负数,正秒数则是延迟执行,负秒数则是提前从动画中途开始执行。 当animation-delay设置为-1s的时候,效果如下:

linear.gif

可以看到我刷新页面的时候,方块是从中间开始运动的,也就是从动画运行了`1s`,第`2s`开始的时候开始播放动画。

实战例子

呼吸灯加载效果

linear.gif

第一步首先将三个点的样式和排版画出来

  <div class="loading">
      <div class="dot1"></div>
      <div class="dot2"></div>
      <div class="dot3"></div>
  </div>
  .loading{
      display: flex;
      align-items: center;
    }
    .loading > div{
      width: 40px;
      height: 40px;
      border-radius: 50%;
      margin: 0 20px;
    }
    .dot1 {
      background: #785aff;
    }

    .dot2 {
      background: #b7a7ff;
      margin: 0 4px;
    }

    .dot3 {
      background: #785aff;
    }

使用上述代码,可以得到如下效果,没有动画的静态样式:

image.png

经过仔细观察,呼吸灯中每个点的动画有两点变化:

  1. 大小发生了变化
  2. 透明度发生了变化

因此我们可以写出如下关键帧:

@keyframes loading {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0.8;
    transform: scale(1.4);
  }
}

初始状态大小不变,不透明度为1,当动画运行结束的时候大小扩大到1.4倍,不透明度变为0.8。因为我们需要无限循环播放动画所以animation-iteration-count置为infinite,动画运行不需要变速,所以animation-timing-function设置为linear,呼吸灯是由小变大,再由大变小,正向反向反复交替执行,所以animation-direction置为alternate,一次动画的运行时间可根据需要来设定,这里就设定为0.6s,也就是animation-duration置为0.6s。我们上述属性设置到呼吸灯的CSS类名下:

    .dot1 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    background: #785aff;
  }
  
  .dot2 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    background: #b7a7ff;
    margin: 0 4px;
  }
  
  .dot3 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    background: #785aff;
  }

得到效果如下:

linear.gif

可以看到大概的效果已经实现了,但是我们想要的是三个呼吸灯交替开始动画,那么这就需要使用到animation-delay延迟一段时间。第二个和第三个呼吸灯递减延迟0.2s,代码如下:

.dot1 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    animation-delay: 0s;
    background: #785aff;
}
  
.dot2 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    background: #b7a7ff;
    animation-delay: 0.2s;
    margin: 0 4px;
}
  
.dot3 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    animation-delay: 0.4s;
    background: #785aff;
}

可以得到如下效果:

linear.gif

这样看似实现了效果实则不然,当我们首次播放动画时,会发现这三个呼吸灯依次开始放大。但实际上,我们希望在首次加载时,这些呼吸灯就能直接显示为加载效果,而不是从初始状态开始逐渐放大。那么我们就可以让后两个呼吸灯在动画的中途开始播放。上边说了元素中途进入动画需要将animation-delay设置为负秒数。代码如下:

.dot1 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    animation-delay: -0.6s;
    background: #785aff;
}
  
.dot2 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    background: #b7a7ff;
    animation-delay: -0.4s;
    margin: 0 4px;
}
.dot3 {
    animation-name: loading;
    animation-duration: 0.6s;
    animation-direction:alternate;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
    animation-delay: -0.2s;
    background: #785aff;
}

解释一下三个呼吸灯设置animation-delay的原因,如果是设置正向延迟时间的话,那么在动画开始时刻就会出现下边的样式:

image.png

为了使三个呼吸灯在首次加载时看起来更加自然,可以设置反向延迟,让动画从中途开始播放。这样,三个呼吸灯的初始状态就会像是加载过程中的某个时刻,从而避免违和感。为了使三个呼吸灯在首次加载时看起来更加自然,可以将动画延迟设置为从中途开始播放。具体来说,可以将之前的正向延迟提前一个动画周期(0.6秒)。这样,原本延迟为 0s 的呼吸灯现在设置为 -0.6s,原本延迟为 0.2s 的呼吸灯现在设置为 -0.4s,原本延迟为 0.4s 的呼吸灯现在设置为 -0.2s。这样,加载时的效果就像是动画过程中的某个时刻,避免了首次加载时的违和感。

总结

本篇文章,通过一个简单的例子,将animation的属性串联了起来,最后通过一个呼吸灯加载实战,来使用这些属性。如果这篇文章对你有帮助请点个免费的小心心哦~(有任何问题在评论区指出) 知识总结完了,后续还会增加几个样例,未完待续.....