纯CSS圆形旋转轮播,图片转圈效果咋搞定

4,313字
18–27 分钟
in

还在用老掉牙的左右滑动轮播图?那种一刀切的切换方式早就看腻了。这次整一个圆形旋转的图片轮播,图片绕着一个大圆圈转,视觉效果直接拉满,而且全程不碰JavaScript,纯CSS就能干出来。下面直接上实操,手把手把代码抠明白。

目录

圆形旋转图片轮播的核心是把所有图片叠在同一位置,每张图绕着同一个圆心旋转,再通过动画延迟和停留时间控制,让图片一张接一张出现在可视区。关键技术点包括transform-origin改变旋转轴心、关键帧动画分段、以及几何计算确定轴心偏移量。本文用4张图和任意数量图两套方案,从HTML结构到Sass动态生成,每一步都有代码和避坑提醒。

圆形轮播咋运作

这个轮播的原理跟旋转木马有点像。想象把四张照片贴在一个大圆盘的边缘,盘心有个柱子,每张照片都对着盘心。整个盘逆时针转,但照片本身不转脸,始终保持正面朝外。实际上代码里不是转整个盘子,而是每张照片独立绕同一个圆心旋转,靠动画延迟让它们依次经过前方的“窗口”。

关键点在于旋转中心。默认情况下图片绕自己的中心转,转出来只会原地打转。必须把旋转中心挪到那个大圆的圆心位置,图片才会画出一个大圆圈。这个偏移量需要通过几何算出来:图片宽S,大圆半径R,满足R = 0.707 * S(当图片数量为4时)。换算成百分比就是transform-origin: 50% 120.7%

4张图片固定版

这套方案代码量最少,适合快速出活或者图片数量固定的场景。

HTML结构

一个容器里面塞四张img,不需要任何多余的div。

<div class="gallery">
  <img src="pic1.jpg" alt="">
  <img src="pic2.jpg" alt="">
  <img src="pic3.jpg" alt="">
  <img src="pic4.jpg" alt="">
</div>

CSS布局

先把所有图片叠到同一个网格区域,大小统一,圆角切一圈。

.gallery {
  --s: 280px;
  display: grid;
  width: var(--s);
  aspect-ratio: 1;
  padding: calc(var(--s) / 20);
  border-radius: 50%;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

这里padding的作用是为后面的彩色圆环边框留出空间。如果不要边框,可以去掉padding,但动画效果依然正常。圆角设成50%让容器变成圆形,图片也会跟着切成圆形,想要方形图片直接删掉border-radius即可。

动画核心

给每张图加上旋转动画,重点改transform-origin

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

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

120.7%这个数值怎么来的?因为半径R占图片宽度的70.7%,旋转轴心的Y坐标就是图片自身高度的一半(50%)加上半径长度(70.7%),等于120.7%。数学推导省略,直接抄作业就行。

光有旋转还不够,四张图同时转,永远只看到最上面那张。需要给每张图加上不同的动画延迟,让它们错开出场。

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

负延迟表示动画从中间某个时刻开始播放,效果就是图片已经提前转到了指定位置。这样四张图均匀分布在圆环的四个象限。

添加停留和缓动

如果不做停留,图片转得太快,根本看不清。需要让每张图在正面停留一小会儿再滑走。改关键帧,把连续旋转拆成带停顿的分段。

@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); }
}

每个90度区间里,前5%是停留时间(比如22%到27%),中间快速转动。停留时长可以通过调整百分比来改,比如把3%改成5%,停留时间就变长了。同时把线性动画换成贝塞尔曲线,转起来更带感。

.gallery > img {
  animation: m 8s infinite cubic-bezier(.5, -0.2, .5, 1.2);
}

这个贝塞尔曲线开头稍微回弹,结尾轻微 overshoot,视觉效果像是有个橡皮筋拽了一下。

边框旋转彩蛋

给容器加一个伪元素,弄个彩色渐变圆环,跟图片一起转。

.gallery {
  position: relative;
}
.gallery::after {
  content: "";
  position: absolute;
  inset: 0;
  padding: inherit;
  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;
  animation: m 8s infinite cubic-bezier(.5, -0.2, .5, 1.2);
}

mask-composite: exclude的作用是只显示padding那一圈区域,内部的图片区域被镂空。动画复用图片的旋转,圆环就和图片同步转起来了。如果不需要边框,直接删掉这段。

调参让动画丝滑

上面关键帧里5%的停留时长和8秒总时长可以自由调整。总时长越短转得越快,停留比例越大看得越清楚。比如想要每张图停留0.6秒、转动0.2秒,四张图一圈总时长就是(0.6+0.2)*4=3.2秒。然后按比例算关键帧:第一张图0%~18.75%停留,18.75%~25%转动,以此类推。算百分比有点麻烦,但好在只需要调一次。

任意数量图片版

如果图片数量不固定,比如3张、5张、8张,上面写死的代码就不够用了。这里用Sass(SCSS)动态生成所有样式。

定义变量$n表示图片总数,$d表示动画总时长(秒)。

$n: 6;
$d: 12s;

动态生成延迟

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

n=6时,第二张延迟(1-2)/6*12 = -2s,第三张-4s,依此类推。

动态生成关键帧

先写出均匀旋转的骨架,再插入停留区间。

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

加上停留时间(假设停留占每段时长的5%,即每个图片区间的5%用来停留)。需要把单个关键帧拆成两个:停留开始和停留结束。

$stop: 5%; // 停留时长占总周期的比例

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

注意第一个停留从0到stop/2,最后一个停留从98%到100%,首尾接起来形成闭环。

通用transform-origin

几何公式:旋转半径R = 50% / sin(180deg / N),所以轴心Y坐标为50% + R = 50% + 50% / sin(180deg / N)

@function origin-y($n) {
  @return 50% + 50% / math.sin(180deg / $n);
}

.gallery > img {
  transform-origin: 50% origin-y($n);
}

Sass中需要先@use "sass:math"。如果不用Sass,也可以手动计算后写死。几个常用值:

图片数量轴心Y坐标
3107.7%
4120.7%
5135.1%
6150.0%
8180.7%

完整Sass代码

$n: 6;
$d: 12s;
$stop: 5%;

.gallery {
  --s: 280px;
  display: grid;
  width: var(--s);
  aspect-ratio: 1;
  padding: calc(var(--s) / 20);
  border-radius: 50%;
  position: relative;
}

.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  animation: m $d infinite cubic-bezier(.5, -0.2, .5, 1.2);
  transform-origin: 50% calc(50% + 50% / sin(180deg / #{$n}));
}

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

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

$n改成实际图片数量,编译出来就能直接用。九张图也照样丝滑转圈,不用复制图片也不用额外HTML,这操作绝了。