CSS 无限循环旋转图片滑块

Avatar of Temani Afif
Temani Afif

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

图片滑块(也称为轮播)无处不在。有很多 创建常见滑块的 CSS 技巧,这些滑块会从左到右(或反方向)滑动图片。与 许多创建带复杂动画的精美滑块的 JavaScript 库 是一样的。我们不会在这篇文章中进行任何操作。

通过一系列文章,我们将探索一些奇特且不常见的纯 CSS 滑块。如果您厌倦了看到相同的经典滑块,那么您来对地方了!

在第一篇文章中,我们将从我称为“循环旋转图片滑块”的东西开始。

很酷,对吧?让我们分析一下代码!

HTML 标记

如果您关注我的 奇特图片装饰CSS 网格和自定义形状 系列,那么您知道我的第一条规则是使用尽可能少的 HTML。我一直努力寻找 CSS 解决方案,而不是用大量<div>和其它内容来使代码混乱。

同样的规则也适用于此 - 我们的代码只不过是在容器中的图片列表。

假设我们使用四张图片

<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

就是这样!现在让我们进入代码中有趣的部分。但首先,我们将深入了解它,以了解滑块工作原理的逻辑。

它是如何工作的?

这里有一段视频,我从中删除了 CSS 中的overflow: hidden,这样我们可以更好地理解图片是如何移动的

就像我们的四张图片被放置在一个逆时针旋转的大圆圈上。

所有图片都具有相同的大小(在图中用S表示)。请注意蓝色圆圈,它是与所有图片中心相交的圆圈,具有半径(R)。我们将在动画中需要此值。R 等于 0.707 * S。(我将跳过给出该方程式的几何学。)

让我们编写一些 CSS!

我们将使用 CSS 网格 将所有图片放置在彼此上方的同一区域中

.gallery  {
  --s: 280px; /* control the size */

  display: grid;
  width: var(--s);
  aspect-ratio: 1;
  padding: calc(var(--s) / 20); /* we will see the utility of this later */
  border-radius: 50%;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

到目前为止,还没有太复杂的内容。棘手的部分是动画。

我们讨论了旋转一个大圆圈,但实际上,我们将单独旋转每个图片,从而产生一个大旋转圆圈的幻觉。因此,让我们定义一个动画m,并将其应用于图片元素

.gallery > img {
  /* same as before */
  animation: m 8s infinite linear;
  transform-origin: 50% 120.7%;
}

@keyframes m {
  100% { transform: rotate(-360deg); }
}

主要技巧依赖于突出显示的那一行。默认情况下,CSS transform-origin 属性等于center(或50% 50%),这使得图片围绕其中心旋转,但我们不需要它那样做。我们需要图片围绕包含我们图片的大圆圈的中心旋转,因此transform-origin 的新值。

由于 R 等于 0.707 * S,因此我们可以说 R 等于图片大小的 70.7%。这里有一张图来说明我们是如何得到120.7% 的值的

让我们运行动画,看看会发生什么

我知道,我知道。结果与我们想要的相差甚远,但实际上我们已经很接近了。可能看起来只有一张图片在那里,但不要忘记我们已经将所有图片叠放在彼此之上。它们都同时旋转,只有最上面的图片可见。我们需要的是延迟每个图片的动画,以避免这种重叠。

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 8s / 4 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 8s / 4 */
.gallery > img:nth-child(4) { animation-delay: -6s; } /* -3 * 8s / 4 */

情况正在好转!

如果我们隐藏容器上的溢出,我们已经可以看到一个滑块,但我们将稍微更新一下动画,以便每个图片在移动之前保持可见一小段时间。

我们将更新动画关键帧来做到这一点

@keyframes m {
  0%, 3% { transform: rotate(0); }
  22%, 27% { transform: rotate(-90deg); }
  47%, 52% { transform: rotate(-180deg); }
  72%, 77% { transform: rotate(-270deg); }
  98%, 100% { transform: rotate(-360deg); }
}

对于每个90deg360deg/4,其中4 是图片数量),我们将添加一个短暂的暂停。每个图片将在移动到下一个图片之前保持可见5% 的总持续时间(27%-22%52%-47% 等)。我将更新animation-timing-function 使用cubic-bezier() 函数使动画更精美

现在我们的滑块很完美!好吧,几乎完美,因为我们还缺少最后一步:围绕我们图片旋转的彩色圆形边框。我们可以使用.gallery 包装器上的伪元素来实现它

.gallery {
  padding: calc(var(--s) / 20); /* the padding is needed here */
  position: relative;
}
.gallery::after {
  content: "";
  position: absolute;
  inset: 0;
  padding: inherit; /* Inherits the same padding */
  border-radius: 50%;
  background: repeating-conic-gradient(#789048 0 30deg, #DFBA69 0 60deg);
  mask: 
    linear-gradient(#fff 0 0) content-box, 
    linear-gradient(#fff 0 0);
  mask-composite: exclude;
}
.gallery::after,
.gallery >img {
  animation: m 8s infinite cubic-bezier(.5, -0.2, .5, 1.2);
}

我使用 重复圆锥渐变 创建了一个圆圈作为背景,同时使用 遮罩技巧,它只显示填充区域。然后,我将与为图片定义的相同动画应用于它。

我们完成了!我们有一个很酷的循环滑块

让我们添加更多图片

使用四张图片很好,但如果我们可以将其扩展到任意数量的图片会更好。毕竟,这是图片滑块的用途。我们应该能够考虑N 张图片。

为此,我们将通过引入 Sass 使代码更通用。首先,我们为图片数量定义一个变量 ($n),并将更新我们对图片数量 (4) 进行硬编码的每个部分。

让我们从延迟开始

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 8s / 4 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 8s / 4 */
.gallery > img:nth-child(4) { animation-delay: -6s; } /* -3 * 8s / 4 */

延迟的公式为 (1 - $i)*duration/$n,这给了我们以下 Sass 循环

@for $i from 2 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    animation-delay: calc(#{(1 - $i) / $n} * 8s);
  }
}

如果我们真的想,我们也可以将持续时间设为变量。但让我们继续动画

@keyframes m {
  0%, 3% { transform: rotate(0); }
  22%, 27% { transform: rotate(-90deg); }
  47%, 52% { transform: rotate(-180deg); }
  72%, 77% { transform: rotate(-270deg); }
  98%, 100% {transform: rotate(-360deg); }
}

让我们简化它,以更好地查看模式

@keyframes m {
  0% { transform: rotate(0); }
  25% { transform: rotate(-90deg); }
  50% { transform: rotate(-180deg); }
  75% { transform: rotate(-270deg); }
  100% { transform: rotate(-360deg); }
}

每个状态之间的步长等于 25% — 也就是 100%/4 — 并且我们添加了一个 -90deg 角 — 也就是 -360deg/4。这意味着我们可以这样编写循环

@keyframes m {
  0% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100}% { transform: rotate(#{($i / $n) * -360}deg); }  
  }
  100% { transform: rotate(-360deg); }
}

由于每个图片占动画的 5%,因此我们将此

#{($i / $n) * 100}%

…改为

#{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}%

应该注意的是,5% 是我为此示例选择的任意值。我们也可以将其设为变量,以控制每个图片应保持可见的时间。为了简单起见,我将跳过这一点,但作为家庭作业,您可以尝试一下,并在评论中分享您的实现!

@keyframes m {
  0%,3% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}% { transform: rotate(#{($i / $n) * -360}deg); }  
  }
  98%,100% { transform: rotate(-360deg); }
}

最后一点是更新transform-origin。我们将需要一些几何技巧。无论图片数量多少,配置始终相同。我们将图片(小圆圈)放置在一个大圆圈内,我们需要找到半径R 的值。

您可能不想要枯燥的几何解释,因此以下是我们如何找到R

R = S / (2 * sin(180deg / N))

如果我们将它表示为百分比,那么它将给出

R = 100% / (2 * sin(180deg / N)) = 50% / sin(180deg / N)

…这意味着transform-origin 值等于

transform-origin: 50% (50% / math.sin(180deg / $n) + 50%);

我们完成了!我们有一个适用于任意数量图片的滑块!

让我们在里面放九张图片

添加任意数量的图片,并将$n 变量更新为图片总数。

总结

通过使用 CSS 变换和标准几何学的几个技巧,我们创建了一个不错的循环滑块,它不需要很多代码。这个滑块的酷炫之处在于,我们不需要为了保持无限动画而费心复制图片,因为我们有一个圆圈。在完整旋转之后,我们将回到第一张图片!