还在用老掉牙的左右滑动轮播图?那种一刀切的切换方式早就看腻了。这次整一个圆形旋转的图片轮播,图片绕着一个大圆圈转,视觉效果直接拉满,而且全程不碰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坐标 |
|---|---|
| 3 | 107.7% |
| 4 | 120.7% |
| 5 | 135.1% |
| 6 | 150.0% |
| 8 | 180.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,这操作绝了。
