实现一个3D轮播图

轮播图大家应该都不陌生了吧,今天我们来实现一个不一样的轮播图。

效果展示

codePen

codepen.io/yongtaozhen...

代码实现

html

左右切换按钮

html 复制代码
<div class="nav-btn prev" onclick="rotate(-1,true)">&#10094;</div>
<div class="nav-btn next" onclick="rotate(1,true)">&#10095;</div>

图片容器

html 复制代码
<div class="spinner"></div>

底部指示器

html 复制代码
<div class="indicators"></div>

css

全局变量与基础布局

css 复制代码
:root {
  --max-width: 180px;       /* 图片最大宽度 */
  --perspective: 1200px;    /* 透视距离,数值越大,3D效果越平缓 */
  --radius: 450px;          /* 图片环绕的圆半径(控制图片间距和纵深) */
}

body {
  display: flex;            /* 居中布局 */
  min-height: 100vh;        /* 占满视口高度 */
  background-color: #f0f0f0; /* 背景色 */
}
  • :root:使用 CSS 变量定义可复用的参数,方便后期修改。
  • perspective:设置在父容器上,模拟观看 3D 场景的距离,数值越大,图片变形越小(类似长焦镜头)。

3D 旋转容器

css 复制代码
.spinner {
  transform-style: preserve-3d; /* 子元素保持3D变换 */
  transform-origin: 50% 50% calc(-1 * var(--radius)); /* 旋转中心点:水平垂直居中,Z轴负方向radius距离 */
  height: 300px;
  transition: transform 0.8s cubic-bezier(0.23, 1, 0.32, 1); /* 旋转过渡动画, cubic-bezier 定义缓动效果 */
}
  • transform-origin:设置旋转中心点在容器中心的正后方(Z 轴负方向),使图片围绕一个虚拟的圆心排列。
  • transition :使用 cubic-bezier(0.23, 1, 0.32, 1) 实现 "先快后慢" 的弹性效果,让旋转更自然。

图片样式

css 复制代码
.spinner img {
  position: absolute;
  left: 50%; top: 50%;        /* 定位到容器中心 */
  width: var(--max-width);
  transform-origin: 50% 50% calc(-1 * var(--radius)); /* 图片自身的旋转中心点与.spinner一致 */
  backface-visibility: hidden; /* 隐藏图片背面,防止旋转时图片翻转显示 */
  box-shadow: 0 10px 25px rgba(0,0,0,0.15); /* 投影增强立体感 */
}
  • backface-visibility: hidden:关键属性!防止图片旋转到背面时出现镜像翻转,确保始终显示正面。
  • transform-origin:与父容器 .spinner 的旋转中心一致,确保图片围绕同一圆心排列。

导航按钮和指示器

css 复制代码
.nav-btn {
  background-color: rgba(255,255,255,0.8); /* 半透明背景 */
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);   /* 悬浮阴影 */
  cursor: pointer;
  z-index: 10;                              /* 确保按钮在图片上方 */
}

.indicator {
  width: 10px; height: 10px;
  background-color: rgba(0,0,0,0.2);        /* 未激活时的指示器样式 */
  transition: background-color 0.3s;        /* 颜色过渡动画 */
}

.indicator.active {
  background-color: rgba(0,0,0,0.6);        /* 激活时的指示器样式 */
}

功能实现

配置参数

javascript 复制代码
const config = {
  maxWidth: "180px",
  speed: 1500, // 轮播速度
  multiple: 1.2, // 鼠标悬停放大倍数
  autoPlay: true, // 是否自动轮播
  imgList: [
    "https://img0.baidu.com/it/u=1446729335,4267600834&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
    "https://img2.baidu.com/it/u=2922694860,2270800253&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
    "https://img0.baidu.com/it/u=595403291,2269048245&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
    ..................
  ],
};
  • maxWidth:每个图片的最大宽度
  • speed:轮播速度,切换的时间频率(ms)
  • multiple:鼠标悬停在图片上的时候,图片放大的倍数
  • autoPlay:是否开启自动轮播
  • imgList:轮播的图片列表

动态创建图片和指示器

根据配置中的图片列表来动态创建图片元素,并设置好旋转角度,根据图片个数创建对于长度的指示器。

javascript 复制代码
config.imgList.forEach((src, index) => {
  const img = document.createElement("img");
  img.src = src;
  img.dataset.index = index;

  // 计算每个图片的旋转角度
  const rotateAngle = -degreesPerImage * index;
  img.style.transform = `rotateY(${rotateAngle}deg) translateZ(450px)`;

  img.addEventListener("mouseenter", handleMouseEnter);
  img.addEventListener("mouseleave", handleMouseLeave);
  img.addEventListener("click", () =>
    handleImageClick((totalImages - index) % totalImages)
  );
  spinner.appendChild(img);

  // 创建指示器
  const indicator = document.createElement("div");
  indicator.className = "indicator" + (index === 0 ? " active" : "");
  indicator.dataset.index = index;
  indicator.addEventListener("click", () => goToSlide(index));
  indicators.appendChild(indicator);
});

自动轮播

根据配置信息中的autoPlay判断是否自动轮播,并在鼠标悬停展示图片的时候停止自动轮播。

javascript 复制代码
function startAutoPlay() {
  if (mouseHover || !config.autoPlay) return;
  autoPlayInterval = setInterval(() => {
    if (!mouseHover) {
      rotate(1);
    }
  }, config.speed);
}

停止自动轮播

鼠标悬停展示图片、指示器切换图片、左右按钮切换图片的时候需要停止自动轮播,清除自动轮播的定时器即可。

javascript 复制代码
function stopAutoPlay() {
  clearInterval(autoPlayInterval);
  autoPlayInterval = null;
}

鼠标移入事件

其实就是图片的hover效果,在鼠标移入图片的时候放大图片,停止图片自动轮播。

javascript 复制代码
function handleMouseEnter(e) {
  const img = e.target;
  img.style.transform = `${img.style.transform} scale(${config.multiple})`;
  img.style.boxShadow = "0 15px 30px rgba(0, 0, 0, 0.25)";
  img.style.zIndex = "10";
  mouseHover = true;
  stopAutoPlay();
}

鼠标离开事件

鼠标移出图片的时候恢复图片原状,并继续自动轮播图片。

javascript 复制代码
function handleMouseLeave(e) {
  const img = e.target;
  const rotateAngle = -degreesPerImage * img.dataset.index;
  img.style.transform = `rotateY(${rotateAngle}deg) translateZ(450px)`;
  img.style.boxShadow = "0 10px 25px rgba(0, 0, 0, 0.15)";
  img.style.zIndex = "1";
  mouseHover = false;
  startAutoPlay();
}

图片点击事件

点击图片的时候停止自动轮播,并将被点击的图片移动到最前面展示。

javascript 复制代码
function handleImageClick(index) {
  stopAutoPlay();
  goToSlide(index);
}

图片切换逻辑

javascript 复制代码
function rotate(direction) {
  const newIndex = (currentIndex + direction + totalImages) % totalImages; // 计算目标索引(处理循环切换)
  const angleDiff = newIndex - currentIndex;

  // 选择最短旋转路径(顺时针或逆时针)
  if (angleDiff > totalImages / 2) angleDiff -= totalImages;
  if (angleDiff < -totalImages / 2) angleDiff += totalImages;

  currentIndex = newIndex;
  angle += angleDiff * degreesPerImage; // 更新总旋转角度
  updateSlide(); // 应用旋转
}

function updateSlide() {
  spinner.style.transform = `rotateY(${-angle}deg)`; // 旋转容器,图片随之移动

  // 更新指示器状态
  indicators.querySelectorAll(".indicator").forEach((ind, i) => {
    ind.classList.toggle("active", i === currentIndex);
  });
}
  • 最短路径算法 :例如 5 张图时,从索引 4 切换到 0 ,最短路径是逆时针转 72° (而非顺时针转 288°)。
  • 负角度旋转rotateY(${-angle}deg) 确保旋转方向与图片排列方向一致(顺时针切换)。

键盘控制

监听键盘方向键,可以通过左右方向键切换图片。

javascript 复制代码
window.addEventListener("keydown", (e) => {
  if (e.key === "ArrowLeft") rotate(-1, true);
  if (e.key === "ArrowRight") rotate(1, true);
});

源码

gitee

gitee.com/zheng_yongt...

github

github.com/yongtaozhen...


  • 🌟 觉得有帮助的可以点个 star~
  • 🖊 有什么问题或错误可以指出,欢迎 pr~
  • 📬 有什么想要实现的功能或想法可以联系我~

公众号

关注公众号『 前端也能这么有趣 』,获取更多有趣内容。

发送 加群 还可以加入群聊,一起来学习(摸鱼)吧~

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

相关推荐
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲5 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter6 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友6 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry7 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js
柳杉8 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化