如何仅使用CSS 创建加载动画

200 阅读8分钟

「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。

在这篇文章中,我们将看到如何只用一行<div>和几行 CSS 代码构建两种类型的加载器。不仅如此,我们还将使它们可定制,以便你可以轻松地从相同的代码创建不同的变体。

image.png

 <div class="loader"></div>
 <div class="loader" style="--b: 15px;--c: blue;width: 120px;--n: 8"></div>
 <div class="loader" style="--b: 5px;--c: green;width: 80px;--n: 6;--g: 20deg"></div>
 <div class="loader" style="--b: 20px;--c: #000;width: 80px;--n: 15;--g: 7deg"></div> 
.loader {
   --b: 10px;  /* border thickness */
   --n: 10;    /* number of dashes*/
   --g: 10deg; /* gap between dashes*/
   --c: red;   /* the color */

   width: 100px; /* size */
   aspect-ratio: 1;
   border-radius: 50%;
   padding: 1px;
   background: conic-gradient(#0000,var(--c)) content-box;
   -webkit-mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
           mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
   -webkit-mask-composite: destination-in;
           mask-composite: intersect;
   animation: load 1s infinite steps(var(--n));
 }
 @keyframes load {to{transform: rotate(1turn)}}

我们有 4 个不同的加载器使用相同的代码。只需更改几个变量,我们就可以生成一个新的加载器,而无需接触 CSS 代码。

变量定义如下:

  • --b定义边框厚度。
  • --n  定义破折号的数量。
  • --g定义破折号之间的间隙。由于我们正在处理圆形元素,因此这是一个角度值。
  • --c定义颜色。

这是查看不同变量的插图。

Spinner 加载器的 CSS 变量

Spinner 加载器的 CSS 变量

让我们处理 CSS 代码。我们将使用另一个图来说明加载程序的逐步构建。

Spinner Loader 的分步说明

Spinner Loader 的分步说明 我们首先创建一个像这样的圆圈:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
}

到目前为止没有什么复杂的。请注意使用aspect-ratiowhich 允许我们只修改一个值(the width)以控制大小。

然后我们添加一个从透明到定义颜色的圆锥渐变着色(变量--c):

.loader {
  width:100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
}

在这一步中,我们引入了mask以重复方式隐藏圆的某些部分的属性。这将取决于--n--d变量。如果你仔细观察该图,我们会注意到以下模式:

visible part
invisible part
visible part
invisible part
etc

为此,我们使用repeating-conic-gradient(#000 0 X, #0000 0 Y). 从0X我们有一个不透明的颜色(可见部分),从XY我们有一个透明的(不可见部分)。

我们引入我们的变量:

  • 我们需要每个可见部分之间的间隙等于 和之间g的公式。X``Y``X = Y - g
  • 我们需要n可见部分,所以公式Y应该是Y = 360deg/n。一个完整的圆圈,360deg所以我们只需将其除以n

到目前为止,我们的代码是:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
  mask: repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n))
}

下一步是最棘手的一步,因为我们需要应用另一个蒙版来创建一种孔以获得最终形状。为此,我们将在逻辑上将 aradial-gradient()与我们的变量一起使用b

radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0)

一个完整的圆圈,我们从中删除等于 的厚度b

我们将它添加到之前的掩码中:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
  mask: 
   radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0),
   repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n))
}

我们有两个遮罩层,但结果不是我们想要的。我们得到以下信息:

image.png 它可能看起来很奇怪,但它是合乎逻辑的。“最终的”可见部分只不过是每个遮罩层的每个可见部分的总和。我们可以使用mask-composite. 我需要整篇文章来解释这个属性,所以我将简单地给出值。

在我们的例子中,我们需要考虑intersect(以及destination-out前缀属性)。我们的代码将变为:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
  mask: 
    radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0),
    repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n));
  -webkit-mask-composite: destination-in;
          mask-composite: intersect;
}

我们完成了形状!我们只是缺少动画。后者是无限旋转。

唯一需要注意的是,我使用steps动画来创建固定虚线和移动颜色的错觉。

这是一个插图,可以看出区别

步骤-最终

线性动画与阶梯动画

第一个是形状的线性和连续旋转(不是我们想要的),第二个是离散动画(我们想要的)。

这是包含动画的完整代码:

<div class="loader"></div>
 <div class="loader" style="--b: 15px;--c: blue;width: 120px;--n: 8"></div>
 <div class="loader" style="--b: 5px;--c: green;width: 80px;--n: 6;--g: 20deg"></div>
 <div class="loader" style="--b: 20px;--c: #000;width: 80px;--n: 15;--g: 7deg"></div> 
.loader {
   --b: 10px;  /* border thickness */
   --n: 10;    /* number of dashes*/
   --g: 10deg; /* gap between dashes*/
   --c: red;   /* the color */

   width: 100px; /* size */
   aspect-ratio: 1;
   border-radius: 50%;
   padding: 1px;
   background: conic-gradient(#0000,var(--c)) content-box;
   -webkit-mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
           mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
   -webkit-mask-composite: destination-in;
           mask-composite: intersect;
   animation: load 1s infinite steps(var(--n));
 }
 @keyframes load {to{transform: rotate(1turn)}}

您会注意到与我在解释中使用的代码有一些不同之处:

  • 我正在添加padding: 1px并将背景设置为content-box
  • +/1deg颜色之间有repeating-conic-gradient()
  • 内部颜色之间有几个百分比的差异radial-gradient()

这些是避免视觉故障的一些更正。众所周知,梯度在某些情况下会产生“奇怪”的结果,因此我们必须手动调整一些值以避免它们。

如何创建进度加载器

和上一个加载器一样,让我们​​从概述开始:

 <div class="loader"></div>
 <div class="loader" style="--s:10px;--n:10;color:red"></div>
 <div class="loader" style="--g:0px;color:darkblue"></div>
 <div class="loader" style="--s:25px;--g:8px;border-radius:50px;color:green"></div>
.loader {
   --n:5;    /* control the number of stripes */
   --s:30px; /* control the width of stripes */
   --g:5px;  /* control the gap between stripes */

   width:calc(var(--n)*(var(--s) + var(--g)) - var(--g));
   height:30px;
   padding:var(--g);
   margin:5px auto;
   border:1px solid;
   background:
     repeating-linear-gradient(90deg,
       currentColor  0 var(--s),
       #0000 0 calc(var(--s) + var(--g))
     ) left / calc((var(--n) + 1)*(var(--s) + var(--g))) 100% 
     no-repeat content-box;
   animation: load 1.5s steps(calc(var(--n) + 1)) infinite;
 }
 @keyframes load {
   0% {background-size: 0% 100%}
 }

我们的配置与之前的加载程序相同。控制加载器的 CSS 变量:

  • --n定义破折号/条纹的数量。
  • --s定义每个条带的宽度。
  • --g定义条纹之间的间隙。

CSS 变量的说明

CSS 变量的说明

从上图中我们可以看出,元素的宽度将取决于 3 个变量。CSS 将如下所示:

.loader {
  width: calc(var(--n)*(var(--s) + var(--g)) - var(--g));
  height: 30px; /* use any value you want here */
  padding: var(--g);
  border: 1px solid;
}

我们padding用来设置每一侧的间隙。那么宽度将等于条纹的数量乘以它们的宽度和间隙。我们删除了一个间隙,因为对于N条纹,我们有N-1间隙。

要创建条纹,我们将使用以下渐变。

repeating-linear-gradient(90deg,
  currentColor 0 var(--s),
  #0000        0 calc(var(--s) + var(--g))
 )

From 0tos是定义的颜色,from stos + g是透明颜色(间隙)。

我正在使用currentColor哪个是color财产的价值。请注意,我没有在内部定义任何颜色,border因此它也将用于color. 如果我们想改变加载器的颜色,我们只需要设置color属性。

到目前为止我们的代码:

.loader {
  width: calc(var(--n)*(var(--s) + var(--g)) - var(--g));
  height: 30px;
  padding: var(--g);
  border: 1px solid;
  background:
    repeating-linear-gradient(90deg,
      currentColor  0 var(--s),
      #0000 0 calc(var(--s) + var(--g))
    ) left / 100% 100% content-box no-repeat;
}

content-box用来确保渐变不覆盖填充区域。然后我定义一个大小等于100% 100%和一个左位置。

动画的时间到了。对于这个加载器,我们将为background-sizefrom 0% 100%to设置动画,100% 100%这意味着我们渐变的宽度 from 0%  to100%

与之前的加载器一样,我们将依赖于steps()离散动画而不是连续动画。

step-2-final

线性动画与阶梯动画

第二个是我们要创建的,我们可以通过添加以下代码来实现:

.loader {
  animation: load 1.5s steps(var(--n)) infinite;
}
@keyframes load {
  0% {background-size: 0% 100%}
}

如果你仔细看最后一张图,你会注意到动画并不完整。即使我们使用了N. 这不是错误,而是steps()应该如何工作。

为了克服这个问题,我们需要添加一个额外的步骤。我们增加background-size渐变的 以包含N+1条纹并使用steps(N+1)。这将使我们得到最终代码:

.loader {
  width: calc(var(--n)*(var(--s) + var(--g)) - var(--g));
  height: 30px;
  padding: var(--g);
  margin: 5px auto;
  border: 1px solid;
  background:
    repeating-linear-gradient(90deg,
      currentColor  0 var(--s),
      #0000 0 calc(var(--s) + var(--g))
    ) left / calc((var(--n) + 1)*(var(--s) + var(--g))) 100% 
    content-box no-repeat;
  animation: load 1.5s steps(calc(var(--n) + 1)) infinite;
}
@keyframes load {
  0% {background-size: 0% 100%}
}

请注意,渐变的宽度等于N+1乘以一个条纹的宽度和一个间隙(而不是100%

结论

我希望你喜欢这个教程。能够帮助你成长。