你不知道的CSS动画

421 阅读11分钟

CSS动画相信大家都非常熟悉,那么我们今天就聊一聊你不熟悉的CSS动画。本文不会讲解CSS动画的基础知识,对于这些内容还不了解的建议先去学习CSS动画的基础知识。

贝塞尔曲线

我们知道,CSS 动画中有一个属性叫做 animation-timing-function ,它定义CSS动画在每一动画周期中执行的节奏。我们平时最常见的几个值就是

animation-timing-function: ease;
animation-timing-function: ease-in;
animation-timing-function: ease-out;
animation-timing-function: ease-in-out;
animation-timing-function: linear;
animation-timing-function: step-start;
animation-timing-function: step-end;

但是不要忘了它还可以接受一个函数:cubic-bezier,就是我们的贝塞尔曲线,它允许我们指定自定义的调速函数。它的语法形式是这样的:cubic-bezier(x_1y_1x_2y_2),其中(x_1y_1)表示第一个控制锚点的坐标,(x_2y_2)表示第二个。这个网站为我们提供了可视化的效果。

曲线片段的两个断点分别固定在(0,0)和(1,1),前者是整个过渡的起点(时间进度为零,动画进度为零),后者是终点(时间进度为100%,动画进度为100%)。

同时,两个控制锚点的x值都被限制在[0, 1]区间内。因为我们目前无法穿越时间,因此无法指定这样一个过渡:在被触发之前就开始了,或者在时间用完以后还没有结束。

但是,重要的是,我们的y值并没有被限制在[0, 1]的区间范围内。大家想想这意味着什么?意味着我们能让动画达到110%的效果。如果我们想用来给动画加一些弹性的效果,这个技巧就非常实用。

比如我们下面这个弹出的效果

在这里插入图片描述
代码是这样的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .demo {
      width: 200px;
      height: 100px;
      background-color: blanchedalmond;
      margin: 0 auto;

      animation: demoAnimation 1s cubic-bezier(.3, .1, .4, 1.5);
    }

    @keyframes demoAnimation {
      0% {transform: scale(0);}
      100% {transform: scale(1);}
    }
  </style>
</head>
<body>
  <div class="demo"></div>
</body>
</html>

还有小球的弹出效果,也可以这样做

在这里插入图片描述
代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .circle {
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background-color: red;

      margin: 100px auto;

      animation: circle 1s cubic-bezier(.3, .1, .4, 1.5);
    }

    @keyframes circle {
      0% {transform: translateY(200px);}
      100% {transform: translateY(0);}
    }
  </style>
</head>
<body>
  <div class="circle"></div>
</body>
</html>

其他更多的效果就靠大家自己去发掘啦!

逐帧动画

逐帧动画同样还是 animation-timing-function 属性,他不仅能接受 cubic-bezier 这个函数,也能接受 steps 函数。

steps 表示能够把动画进行分段执行,不再平滑执行,即变成逐帧动画。它的语法是这样的steps(x, start | end)。第一个参数x表示分成了几段(注意:不是把整个动画分成了几段),第二个参数的含义我们后面再说。如果你现在还不能理解也没关系,我们继续往后看,用例子来解释这两个参数。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>

  <style>
    body {
      background-color: red;

      animation: step 5s steps(1, start) infinite;
    }

    @keyframes step {
      0% {background-color: red;}
      50% {background-color: yellow;}
      100% {background-color: blue;}
    }
  </style>
</head>
<body>
  
</body>
</html>

在这个例子中,我们 steps 的参数为 1 和 start。1 代表0-50%的动画被分成了一段,50%-100%的动画被分成了一段。所以整个动画会被分成几段?即原先的段数乘x。在这里我们的动画原先被分成了两段,x = 1,所以这里就是那 2*1 = 2 段。 那start 是什么含义呢?

我们如果不加 steps ,我们的动画效果就是从红色往黄色进行过渡,相信大家对这个都非常熟悉了。

但是我们加了 setps 函数之后,我们的动画效果就不是过渡了。我们取每一段动画的最后一帧,如果第二个参数为 start 那么从每一段的开始,就会显示最后一帧的动画。如果参数为 end ,那么每一段的最后,就会显示最后一帧的效果。

用图片说话就是,如果我们的参数为 start 整个动画就变成这个效果:

0-50%的最后一帧是黄色,参数为 start ,即每段一开始就会变成最后一帧,所以0-50%之间都是黄色。同理,50%-100%之间全是蓝色。所以我们的动画就会变成黄色 -> 蓝色,红色就不会出现在我们的动画之中了。

那如果我们把参数改成 end 呢?那就是下面这样

0-50%的最后一帧是黄色,但是参数为 end ,所以在每段开始的时候并不显示最后一帧。所以0-50%就是最初的颜色红色。到了50%-100%,即到了0-50%的最后,会开始显示黄色。此时蓝色将会影响下一段,但是我们的动画已经结束了,所以蓝色不会出现在我们的动画中。

我们来看看实际的效果:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>

  <style>
    body {
      background-color: red;

      animation: step 1s steps(1, end) infinite;
    }

    @keyframes step {
      0% {background-color: red;}
      50% {background-color: yellow;}
      100% {background-color: blue;}
    }
  </style>
</head>
<body>
  
</body>
</html>

第二个参数为 start :

在这里插入图片描述
第二个参数为 end:
在这里插入图片描述
我们为什么用 1 来举例子?这是为了方便大家理解,并且便于直观地看出效果。如果还不理解,可以把 x 改成其他的数字来看看会产生什么效果。同时,如果参数为1,我们可以不使用 steps ,step-start 就等同于 steps(1, start),step-end 就等同于 steps(1, end)。所以我们可以直接这样写:

animation-timing-function: step-start;
animation-timing-function: step-end;

搞清楚这个以后,你可能会想,这有什么用呢?用处可大了,在某些时候,我们就想要逐帧的动画,而不想要连续的动画。

在这里插入图片描述
看,这就是逐帧的动画,我们让这幅图片逐帧旋转起来就是这种效果
在这里插入图片描述
同时,我们也能用 steps 来实现闪烁效果,就像我们最开始举的例子。

静止的动画

看到这个标题你可能会纳闷,动画动画,就是要动起来,静止的动画又是个什么操作?且听我细细道来。

首先,我们先用 CSS 来实现一个饼图,就像下面这样:

在这里插入图片描述
这里我大致说一下思路,就不详细解释了:

首先我们构造一个二分的饼图,这里用到了渐变:

然后再用伪元素遮住右边的部分:
在这里插入图片描述
为了方便辨认我用黑色边框把伪元素展示出来。

最后我们再旋转伪元素,漏出一部分蓝色背景:

在这里插入图片描述
这样一个饼图就完成啦!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .pie {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: linear-gradient(to right, red 50%, blue 0);

      margin: 100px auto;
    }

    .pie::before {
      content: "";
      display: block;
      margin-left: 50px;
      width: 50px;
      height: 100px;
      background-color: red;
      /* border: 1px solid black; */
      border-radius: 0 100% 100% 0 / 50%;

      transform: rotate(.3turn);
      transform-origin: 0 50%;
    }
  </style>
</head>
<body>
  <div class="pie">

  </div>
</body>
</html>

这样显示50%以下的内容完全没有问题,那50%以上呢?其实我们只要把伪元素的颜色改一下就好了

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .pie {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: linear-gradient(to right, red 50%, blue 0);

      margin: 100px auto;
    }

    .pie::before {
      content: "";
      display: block;
      margin-left: 50px;
      width: 50px;
      height: 100px;
      background-color: blue;
      /* border: 1px solid black; */
      border-radius: 0 100% 100% 0 / 50%;

      transform: rotate(.2turn);
      transform-origin: 0 50%;
  </style>
</head>
<body>
  <div class="pie">

  </div>
</body>
</html>

这样就能显示70%的饼图啦

那么问题来了,这两种饼图我们都能表示了,那我们如何在实际中去使用呢?难道每次都得写这么一大堆东西吗?

我们的解决方法可能让你意向不到,对,就是用动画!用静止的动画。首先我们来写一个饼图旋转的动画:

在这里插入图片描述
代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .pie {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: linear-gradient(to right, red 50%, blue 0);

      margin: 100px auto;
    }

    .pie::before {
      content: "";
      display: block;
      margin-left: 50px;
      width: 50px;
      height: 100px;
      background-color: red;
      /* border: 1px solid black; */
      border-radius: 0 100% 100% 0 / 50%;

      transform-origin: 0 50%;
      animation: pie 1s infinite linear, bg 2s infinite step-end;
    }

    @keyframes pie {
      0% {transform: rotate(0)}
      100% {transform: rotate(.5turn)}
    }

    @keyframes bg {
      50% {background-color: blue;}
    }
  </style>
</head>
<body>
  <div class="pie">

  </div>
</body>
</html>

这里又用到了我们神奇的 step-end !,因为我们需要改变伪元素的背景,但是又不想让它有过渡效果,所以这个函数在这里非常好用。

好了,我们现在有了一个从0-100%改变的饼图动画。我们知道,动画是可以停止的,但是有没有办法让动画跳到某一帧然后直接停止呢?有!动画中有一个延时属性:animation-delay 。我们平时用的最多的肯定是给他赋一个正值,殊不知,这个属性也接受负值。

一个负的延时值是合法的。与0s的延时类似,它意味着动画会立即开始播放,但会自动前进到延时值的绝对值处,就好像动画在过去已经播放了指定的时间一样。因此实际效果就是动画跳过指定时间而从中间开始播放了。

good!下面我们结合 animation-delay 和 animation-play-state 来做一个静止的动画。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .pie {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: linear-gradient(to right, red 50%, blue 0);

      margin: 100px auto;
    }

    .pie::before {
      content: "";
      display: block;
      margin-left: 50px;
      width: 50px;
      height: 100px;
      background-color: red;
      /* border: 1px solid black; */
      border-radius: 0 100% 100% 0 / 50%;

      transform-origin: 0 50%;
      animation: pie 10s infinite linear, bg 20s infinite step-end;

      animation-play-state: paused;
      animation-delay: -17s;
    }

    @keyframes pie {
      0% {transform: rotate(0)}
      100% {transform: rotate(.5turn)}
    }

    @keyframes bg {
      50% {background-color: blue;}
    }
  </style>
</head>
<body>
  <div class="pie">

  </div>
</body>
</html>

看!我们成功实现了一个静止的动画

在这里插入图片描述
下面我们让伪元素直接继承父元素的 delay 值,那样我们就能用最简单的方式创造各种各样的饼图:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .pie {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: linear-gradient(to right, red 50%, blue 0);

      margin: 100px auto;
    }

    .pie::before {
      content: "";
      display: block;
      margin-left: 50px;
      width: 50px;
      height: 100px;
      background-color: red;
      /* border: 1px solid black; */
      border-radius: 0 100% 100% 0 / 50%;

      transform-origin: 0 50%;
      animation: pie 50s infinite linear, bg 100s infinite step-end;

      animation-play-state: paused;
      animation-delay: inherit;
    }

    @keyframes pie {
      0% {transform: rotate(0)}
      100% {transform: rotate(.5turn)}
    }

    @keyframes bg {
      50% {background-color: blue;}
    }
  </style>
</head>
<body>
  <div class="pie" style="animation-delay: -50s;">

  </div>

  <div class="pie" style="animation-delay: -40s;">

  </div>

  <div class="pie" style="animation-delay: -70s;">

  </div>
</body>
</html>

结语

本文的内容来自《CSS 揭秘》,自己在看完这本书之后觉得这几个小技巧非常巧妙又实用,所以来分享给大家。同时书中还有很多类似的小技巧,后期如果大家感兴趣我可以继续整理然后分享给大家~最后,不要吝啬你手中的赞和关注,您的支持就是我最大的动力!