单元素加载器:迈向 3D!

Avatar of Temani Afif
Temani Afif

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

在本系列关于单元素加载器的第四篇也是最后一篇文章中,我们将探索 3D 模式。在创建 3D 元素时,很难想象仅使用一个 HTML 元素就能模拟立方体的六个面。但也许我们可以通过仅显示形状的前三个面来获得更类似立方体的效果——这完全有可能,我们也将一起尝试。

文章系列

分割立方体加载器

这是一个 3D 加载器,其中一个立方体被分成两部分,但仅使用一个元素制作

立方体的每一半都是使用伪元素制作的

很酷,对吧?!我们可以使用圆锥形渐变和 CSS clip-path 在元素的 ::before::after 伪元素上模拟 3D 立方体的三个可见面。负边距将这两个伪元素拉到一起重叠并模拟一个完整的立方体。我们其余的工作主要是在动画这两个部分,以获得美观的加载器!

让我们查看一个可视化图表,解释用于创建此类似立方体的元素的剪辑路径点的背后数学原理

我们有变量和方程式,所以让我们开始使用它们。首先,我们将建立我们的变量并设置主 .loader 元素的大小

.loader {
  --s: 150px; /* control the size */
  --_d: calc(0.353 * var(--s)); /* 0.353 = sin(45deg)/2 */

  width: calc(var(--s) + var(--_d)); 
  aspect-ratio: 1;
  display: flex;
}

到目前为止,没什么太疯狂的。我们有一个 150px 的正方形,它被设置为一个灵活的容器。现在我们建立我们的伪元素

.loader::before,
.loader::after {
  content: "";
  flex: 1;
}

这是 .loader 容器中的两个部分。我们需要为它们着色,这就是我们的 圆锥形渐变 派上用场的地方

.loader::before,
.loader::after {
  content: "";
  flex: 1;
  background:
    conic-gradient(from -90deg at calc(100% - var(--_d)) var(--_d),
    #fff 135deg, #666 0 270deg, #aaa 0);
}

渐变在那里,但是 看起来很奇怪。我们需要 将其剪辑到元素

.loader::before,
.loader::after {
  content: "";
  flex: 1;
  background:
    conic-gradient(from -90deg at calc(100% - var(--_d)) var(--_d),
    #fff 135deg, #666 0 270deg, #aaa 0);
  clip-path:
    polygon(var(--_d) 0, 100% 0, 100% calc(100% - var(--_d)), calc(100% - var(--_d)) 100%, 0 100%, 0 var(--_d));
}

让我们确保这两个部分通过 负边距 重叠

.loader::before {
  margin-right: calc(var(--_d) / -2);
}

.loader::after {
  margin-left: calc(var(--_d) / -2);
}

现在让我们让它们动起来!

.loader::before,
.loader::after {
  /* same as before */
  animation: load 1.5s infinite cubic-bezier(0, .5, .5, 1.8) alternate;
}

.loader::after {
  /* same as before */
  animation-delay: -.75s
}

@keyframes load{
  0%, 40%   { transform: translateY(calc(var(--s) / -4)) }
  60%, 100% { transform: translateY(calc(var(--s) / 4)) }
}

这是最终演示

进度立方体加载器

让我们使用相同的技术创建一个 3D 进度加载器。是的,仍然只有一个元素!

除了更改加载器的高度和纵横比之外,我们不会更改任何模拟立方体的方式。我们正在制作的动画依赖于一种非常简单的技术,即在右侧填充剩余空间的同时更新左侧的宽度,这要归功于 flex-grow: 1

第一步是使用 opacity 为右侧添加一些透明度

这模拟了立方体的一侧填充而另一侧为空的效果。然后我们更新左侧的颜色。为此,我们可以更新圆锥形渐变中的三种颜色,或者通过添加具有 background-blend-mode 的背景颜色来实现。

.loader::before {
  background-color: #CC333F; /* control the color here */
  background-blend-mode: multiply;
}

此技巧仅允许我们更新一次颜色。加载器的右侧与圆锥形渐变中的三种白色阴影混合,创建了我们颜色的三种新阴影,即使我们只使用一个颜色值。颜色技巧!

让我们为加载器的左侧宽度设置动画

糟糕,动画在开始时有点奇怪!注意它如何从立方体外部开始?这是因为我们从 0% 宽度开始动画。但是由于我们正在使用的 clip-path 和负边距,我们需要做的是从我们的 --_d 变量开始,我们用它来定义 clip-path 点和负边距

@keyframes load {
  0%,
  5% {width: var(--_d); }
  95%,
  100% {width: 100%; }
}

这样好多了

但是我们可以使此动画更加流畅。您是否注意到我们缺少了一些东西?让我向您展示一个屏幕截图,以比较最终演示与最后一个演示的外观

它是立方体的底面!由于第二个元素是透明的,因此您需要看到该矩形的底面,如左侧示例所示。它很微妙,但应该存在!

我们可以向主元素添加渐变并像对伪元素一样剪辑它

background: linear-gradient(#fff1 0 0) bottom / 100% var(--_d) no-repeat;

这是将所有内容整合在一起后的完整代码

.loader {
  --s: 100px; /* control the size */
  --_d: calc(0.353*var(--s)); /* 0.353 = sin(45deg) / 2 */

  height: var(--s); 
  aspect-ratio: 3;
  display: flex;
  background: linear-gradient(#fff1 0 0) bottom / 100% var(--_d) no-repeat;
  clip-path: polygon(var(--_d) 0, 100% 0, 100% calc(100% - var(--_d)), calc(100% - var(--_d)) 100%, 0 100%, 0 var(--_d));
}
.loader::before,
.loader::after {
  content: "";
  clip-path: inherit;
  background:
    conic-gradient(from -90deg at calc(100% - var(--_d)) var(--_d),
     #fff 135deg, #666 0 270deg, #aaa 0);
}
.loader::before {
  background-color: #CC333F; /* control the color here */
  background-blend-mode: multiply;
  margin-right: calc(var(--_d) / -2);
  animation: load 2.5s infinite linear;
}
.loader:after {
  flex: 1;
  margin-left: calc(var(--_d) / -2);
  opacity: 0.4;
}

@keyframes load {
  0%,
  5% { width: var(--_d); }
  95%,
  100% { width: 100%; }
}

就是这样!我们刚刚使用了一种巧妙的技术,该技术使用伪元素、圆锥形渐变、剪辑、背景混合和负边距来获得,不是一个,而是两个外观漂亮的 3D 加载器,而标记中只有一个元素。

更多 3D

我们还可以更进一步,使用一个元素模拟无限数量的 3D 立方体——是的,这是可能的!这是一个立方体网格

在撰写本文时,此演示和以下演示在 Safari 中不受支持。

太疯狂了,对吧?现在我们正在使用单个元素创建重复的立方体图案……而且没有使用伪元素!我不会详细介绍我们正在使用的数学运算(其中有非常具体的数字),但这里有一个图形来可视化我们是如何到达这里的

我们首先使用 conic-gradient 创建重复的立方体图案。图案的重复由三个变量控制

  • --size:顾名思义,它控制每个立方体的大小。
  • --m:这表示列数。
  • --n:这是行数。
  • --gap:这是立方体之间的间隙或距离
.cube {
  --size: 40px; 
  --m: 4; 
  --n: 5;
  --gap :10px;

  aspect-ratio: var(--m) / var(--n);
  width: calc(var(--m) * (1.353 * var(--size) + var(--gap)));
  background:
    conic-gradient(from -90deg at var(--size) calc(0.353 * var(--size)),
      #249FAB 135deg, #81C5A3 0 270deg, #26609D 0) /* update the colors here */
    0 0 / calc(100% / var(--m)) calc(100% / var(--n));
}

然后我们使用另一个具有相同大小的图案应用蒙版层。这是这个想法中最棘手的部分。通过结合使用 linear-gradientconic-gradient,我们将剪切元素的某些部分,以仅保留可见的立方体形状。

.cube {
  /* etc. */
  mask: 
    linear-gradient(to bottom right,
       #0000 calc(0.25 * var(--size)),
       #000 0 calc(100% - calc(0.25 * var(--size)) - 1.414 * var(--gap)),
       #0000 0),
    conic-gradient(from -90deg at right var(--gap) bottom var(--gap), #000 90deg, #0000 0);  
  mask-size: calc(100% / var(--m)) calc(100% / var(--n));
  mask-composite: intersect;
}

代码可能看起来有点复杂,但由于 CSS 变量,我们只需更新一些值即可控制我们的立方体矩阵。需要一个 10⨉10 网格?将 --m--n 变量更新为 10。需要立方体之间有更大的间隙?更新 --gap 值。颜色值仅使用一次,因此请更新它们以获得新的调色板!

既然我们有了另一种 3D 技术,让我们通过玩转不同的动画来使用它构建加载器的变体。例如,如何创建一个从左到右无限滑动的重复立方体图案?

此加载器在一行中定义了四个立方体。这意味着我们的 --n 值为 4--m 等于 1。换句话说,我们不再需要这些了!

相反,我们可以在网格容器中使用 --size--gap 变量

.loader {
  --size: 70px;
  --gap: 15px;  

  width: calc(3 * (1.353 * var(--size) + var(--gap)));
  display: grid;
  aspect-ratio: 3;
}

这是我们的容器。我们有四个立方体,但每次只想在容器中显示三个,以便我们始终有一个滑入,一个滑出。这就是为什么我们将宽度乘以 3 并将纵横比也设置为 3 的原因。

让我们确保我们的立方体图案设置为四个立方体的宽度。我们将在容器的 ::before 伪元素上执行此操作

.loader::before { 
  content: "";
  width: calc(4 * 100% / 3);
  /*
     Code to create four cubes
  */
}

现在我们在一个三立方体容器中有了四个立方体,我们可以将立方体图案对齐到网格容器的末端以使其溢出,从而显示最后三个立方体

.loader {
  /* same as before */
  justify-content: end;
}

到目前为止,我们已经有了这些,并用红色轮廓显示网格容器的边界

现在我们要做的就是通过添加动画将伪元素向右移动

@keyframes load {
  to { transform: translate(calc(100% / 4)); }
}

你理解动画的技巧了吗?让我们通过隐藏溢出的立方体图案并添加一些蒙版来创建开始和结束处的淡出效果来完成它

.loader {
  --size: 70px;
  --gap: 15px;  
  
  width: calc(3*(1.353*var(--s) + var(--g)));
  display: grid;
  justify-items: end;
  aspect-ratio: 3;
  overflow: hidden;
  mask: linear-gradient(90deg, #0000, #000 30px calc(100% - 30px), #0000);
}

我们可以通过引入一个变量 --n 来使它更灵活,以设置容器中一次显示多少个立方体。并且由于图案中的立方体总数应比 --n 多一个,因此我们可以将其表示为 calc(var(--n) + 1)

这是完整的内容

好的,再做一个类似的 3D 加载器,但立方体依次更改颜色而不是滑动

我们将依靠动画背景和 background-blend-mode 来实现此目的

.loader {
  /* ... */
  background:
    linear-gradient(#ff1818 0 0) 0% / calc(100% / 3) 100% no-repeat,
    /* ... */;
  background-blend-mode: multiply;
  /* ... */
  animation: load steps(3) 1.5s infinite;
}
@keyframes load {
  to { background-position: 150%; }
}

我已经删除了用于创建与上一个示例相同的布局的冗余代码,但使用了三个立方体而不是四个。我在这里添加的是一个使用特定颜色定义的渐变,该渐变与圆锥形渐变混合,就像我们之前对进度条 3D 加载器所做的那样。

从那里,它将背景渐变的 background-position 作为三步动画设置动画,以使立方体一次一个地闪烁颜色。

如果你不熟悉我用于background-position和背景语法的值,我强烈建议你阅读我之前的一篇文章以及我在Stack Overflow上的回答之一。在那里你会找到非常详细的解释。

我们可以将立方体的数量更新为变量吗?

是的,我确实有一个解决方案,但我希望你尝试一下,而不是直接嵌入在这里。借鉴我们从上一个例子中学到的知识,尝试用这个例子做同样的事情——然后在评论中分享你的成果!

变化多多!

就像本系列中的其他三篇文章一样,我想给你一些灵感,让你继续创造自己的加载器。这里有一系列作品,包括我们一起制作的3D加载器,以及其他一些作品,以激发你的想象力。

总结

我真的很希望你在过去几周里和我一起制作单元素加载器度过了愉快的时光。令人难以置信的是,我们从看似简单的旋转器开始,然后逐渐添加新的元素,最终使用3D技术,但在标记中仍然只使用一个元素。这正是当我们利用CSS的力量时,它所展现的样子:可扩展、灵活且可重用。

再次感谢你阅读这个小系列!我将在结束时提醒你,如果你正在寻找更多想法和灵感,我有一个包含500多个加载器的集合

文章系列