手写js轮播图效果参考

话不多说,直接干货

1.css样式表

css 复制代码
/* === 轮播图样式 === */
.carousel-container {
  position: relative; /* 为绝对定位的按钮和指示器提供参考 */
  max-width: 800px;
  margin: 20px auto;
  overflow: hidden; /* 隐藏超出容器的部分 */
  border-radius: 8px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

.carousel {
  display: flex; /* 核心:让图片水平排列 */
  transition: transform 0.5s ease-in-out; /* 核心:为切换添加平滑的滑动动画 */
}

.carousel-item {
  min-width: 100%; /* 核心:每个项目占满整个容器宽度 */
  height: 300px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 2.5rem;
  font-weight: bold;
  color: white;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}

.carousel-item img {
    width: 100%;
    height: 300px;
}

/* 轮播图按钮样式 */
.carousel-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  padding: 15px 20px;
  font-size: 1.2rem;
  cursor: pointer;
  border-radius: 0 4px 4px 0; /* 左边按钮圆角在右侧 */
  transition: background-color 0.3s;
  z-index: 10;
}

.carousel-btn:hover {
  background-color: rgba(0, 0, 0, 0.8);
}

.prev-btn {
  left: 0;
  border-radius: 0 4px 4px 0;
}

.next-btn {
  right: 0;
  border-radius: 4px 0 0 4px;
}

/* 底部指示器样式 */
.carousel-indicators {
  position: absolute;
  bottom: 15px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 10px;
  z-index: 10;
}

.indicator {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: background-color 0.3s, transform 0.3s;
}

.indicator.active {
  background-color: white;
  transform: scale(1.2); /* 激活的指示器稍大 */
}

2.js轮播逻辑

javascript 复制代码
// ==================== 轮播图功能 (无缝滚动版) ====================

document.addEventListener("DOMContentLoaded", () => {
  console.log("轮播图功能");
  // --- 1. 获取DOM元素 ---
  const carousel = document.querySelector(".carousel");
  const items = document.querySelectorAll(".carousel-item"); // 注意:这是初始的items
  const prevBtn = document.querySelector(".prev-btn");
  const nextBtn = document.querySelector(".next-btn");
  const indicatorsContainer = document.querySelector(".carousel-indicators");

  // --- 2. 初始化变量 ---
  let currentIndex = 0; // 当前显示的图片索引 (基于原始列表)
  const totalItems = items.length;
  let autoPlayInterval;
  let isTransitioning = false; // 防止在动画过程中重复点击

  // --- 3. 克隆首尾元素以实现无缝滚动 ---
  const firstItemClone = items[0].cloneNode(true);
  const lastItemClone = items[totalItems - 1].cloneNode(true);

  // 将克隆项添加到DOM中
  carousel.appendChild(firstItemClone);
  carousel.insertBefore(lastItemClone, items[0]);

  // 重新获取所有items,因为DOM已经改变
  const newItems = document.querySelectorAll(".carousel-item");
  const newTotalItems = newItems.length;

  // 初始化位置,显示第一个真实项 (即克隆的最后一项之后的那一项)
  carousel.style.transform = `translateX(-${100}%)`;

  // --- 4. 创建指示器 (基于原始数量) ---
  items.forEach((_, index) => {
    // 注意:这里依然用原始的items来循环
    const indicator = document.createElement("div");
    indicator.classList.add("indicator");
    if (index === 0) {
      indicator.classList.add("active");
    }
    indicator.addEventListener("click", () => {
      if (isTransitioning) return;
      goToSlide(index);
      resetAutoPlay();
    });
    indicatorsContainer.appendChild(indicator);
  });

  const indicators = document.querySelectorAll(".indicator");

  // --- 5. 核心函数 ---

  /**
   * 更新轮播图位置和指示器状态
   * @param {number} index - 目标图片的索引 (基于原始列表)
   * @param {boolean} disableTransition - 是否禁用过渡动画(用于瞬间跳转)
   */
  const updateCarousel = (index, disableTransition = false) => {
    if (disableTransition) {
      carousel.style.transition = "none";
    } else {
      carousel.style.transition = "transform 0.5s ease-in-out";
    }

    // 计算偏移量。注意:因为前面多了一个克隆项,所以是 (index + 1)
    const offset = -(index + 1) * 100;
    carousel.style.transform = `translateX(${offset}%)`;

    // 更新指示器状态
    indicators.forEach((indicator, i) => {
      indicator.classList.toggle("active", i === index);
    });
  };

  /**
   * 跳转到指定的幻灯片
   * @param {number} index - 目标索引 (基于原始列表)
   */
  const goToSlide = (index) => {
    if (index < 0 || index >= totalItems) return;
    currentIndex = index;
    updateCarousel(currentIndex);
  };

  /**
   * 处理滑动到下一张的逻辑
   */
  const nextSlide = () => {
    if (isTransitioning) return;
    isTransitioning = true;
    currentIndex++;

    updateCarousel(currentIndex);

    // 如果滚动到了克隆的第一项,动画结束后跳转到真实的第一项
    if (currentIndex === totalItems) {
      setTimeout(() => {
        currentIndex = 0;
        updateCarousel(currentIndex, true); // 瞬间跳转,无动画
        isTransitioning = false;
      }, 500); // 这个时间必须和CSS中的transition时间一致
    } else {
      setTimeout(() => {
        isTransitioning = false;
      }, 500);
    }
  };

  /**
   * 处理滑动到上一张的逻辑
   */
  const prevSlide = () => {
    if (isTransitioning) return;
    isTransitioning = true;
    currentIndex--;

    updateCarousel(currentIndex);

    // 如果滚动到了克隆的最后一项,动画结束后跳转到真实的最后一项
    if (currentIndex === -1) {
      setTimeout(() => {
        currentIndex = totalItems - 1;
        updateCarousel(currentIndex, true); // 瞬间跳转,无动画
        isTransitioning = false;
      }, 500); // 这个时间必须和CSS中的transition时间一致
    } else {
      setTimeout(() => {
        isTransitioning = false;
      }, 500);
    }
  };

  // --- 自动播放、停止、重置函数 (与之前相同) ---
  const startAutoPlay = () => {
    autoPlayInterval = setInterval(nextSlide, 3000);
  };

  const stopAutoPlay = () => {
    clearInterval(autoPlayInterval);
  };

  const resetAutoPlay = () => {
    stopAutoPlay();
    startAutoPlay();
  };

  // --- 6. 绑定事件监听器 ---
  nextBtn.addEventListener("click", () => {
    if (isTransitioning) return;
    nextSlide();
    resetAutoPlay();
  });

  prevBtn.addEventListener("click", () => {
    if (isTransitioning) return;
    prevSlide();
    resetAutoPlay();
  });

  carousel.addEventListener("mouseenter", stopAutoPlay);
  carousel.addEventListener("mouseleave", startAutoPlay);

  // 监听过渡结束事件,这是一种更健壮的方式
  carousel.addEventListener("transitionend", () => {
    isTransitioning = false;
  });

  // --- 7. 初始化 ---
  startAutoPlay();
});
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>轮播图</title>
    <link rel="stylesheet" href="./swiper.css" />
    <script lang="javascript" src="./swiper.js"></script>
  </head>
  <body>
        <!-- 轮播图 -->
        <section class="demo-section">
          <h2>可控轮播图</h2>
          <div class="demo-content">
            <p>这是一个支持自动播放、手动切换和指示器跳转的轮播图。</p>
            <div class="carousel-container">
              <!-- 轮播图主体 -->
              <div class="carousel">
                <!-- 轮播图项,这里用背景色和文字代替图片 -->
                <div class="carousel-item active">
                  <img
                    src="https://res.cloudinary.com/dx6iqjba3/image/upload/v1755157950/zjj_x07psy.png"
                    alt=""
                  />
                </div>
                <div class="carousel-item">
                  <img
                    src="https://res.cloudinary.com/dx6iqjba3/image/upload/v1764596177/yushu_zy5o3a.png"
                    alt=""
                  />
                </div>
                <div class="carousel-item">
                  <img
                    src="https://res.cloudinary.com/dx6iqjba3/image/upload/v1764596745/hongbei_wpxxq0.png"
                    alt=""
                  />
                </div>
                <div class="carousel-item">
                  <img
                    src="https://res.cloudinary.com/dx6iqjba3/image/upload/v1755160584/jrsp_p1mapm.png"
                    alt=""
                  />
                </div>
              </div>

              <!-- 左右切换按钮 -->
              <button class="carousel-btn prev-btn">&#10094;</button>
              <button class="carousel-btn next-btn">&#10095;</button>

              <!-- 底部指示器 -->
              <div class="carousel-indicators">
                <!-- 指示器将由JS动态生成 -->
              </div>
            </div>
          </div>
        </section>
   
  </body>
</html>

轮播图虽然是一个常见的UI组件,但其中蕴含的DOM操作、CSS动画和状态管理技巧对前端开发者来说是很好的学习材料。理解这些原理有助于我们在日常开发中创建更高效、更健壮的交互组件。

相关推荐
Swift社区2 小时前
Vue Router 越写越乱,如何架构设计?
前端·javascript·vue.js
白兰地空瓶2 小时前
JavaScript 列表转树(List to Tree)详解:前端面试中如何从递归 O(n²) 优化到一次遍历 O(n)
javascript·算法·面试
思成不止于此2 小时前
C++ STL中map与set的底层实现原理深度解析
开发语言·c++·set·map·红黑树·底层实现
大布布将军2 小时前
⚡️ 后端工程师的护甲:TypeScript 进阶与数据建模
前端·javascript·程序人生·typescript·前端框架·node.js·改行学it
惺忪97982 小时前
C++ 构造函数完全指南
开发语言·c++
小此方2 小时前
Re:从零开始学C++(五)类和对象·第二篇:构造函数与析构函数
开发语言·c++
秦苒&2 小时前
【C语言】详解数据类型和变量(二):三种操作符(算数、赋值、单目)及printf
c语言·开发语言·c++·c#
无限进步_2 小时前
【C语言&数据结构】有效的括号:栈数据结构的经典应用
c语言·开发语言·数据结构·c++·git·github·visual studio
是喵斯特ya2 小时前
python开发web暴力破解工具(进阶篇 包含验证码识别和token的处理)
开发语言·python·web安全