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

相关推荐
江城开朗的豌豆32 分钟前
JavaScript篇:构造函数 vs Class:谁才是对象创建的王者?
前端·javascript·面试
江城开朗的豌豆35 分钟前
JavaScript篇:数组找不同:如何快速找出两个数组间的'单身狗'元素?
前端·javascript·面试
几道之旅36 分钟前
python-pptx去除形状默认的阴影
开发语言·javascript·python
不吃鱼的羊1 小时前
ISOLAR软件生成报错处理(七)
java·前端·javascript
TE-茶叶蛋1 小时前
React-props
前端·javascript·react.js
安分小尧1 小时前
[特殊字符] 超强 Web React版 PDF 阅读器!支持分页、缩放、旋转、全屏、懒加载、缩略图!
前端·javascript·react.js
EndingCoder2 小时前
React从基础入门到高级实战:React 高级主题 - React Concurrent 特性:深入探索与实践指南
前端·javascript·react.js·前端框架
EndingCoder2 小时前
React从基础入门到高级实战:React 生态与工具 - React Query:异步状态管理
前端·javascript·react.js·前端框架
TE-茶叶蛋2 小时前
ReactJS 中的 JSX工作原理
前端·react.js·前端框架
水煮白菜王2 小时前
React 编译器
前端·react.js·前端框架