Skip to content

Swiper 轮播框

在有限空间内,循环播放同一类型的图片、文字等内容。

CSS样式

全部 swiper 样式代码:

查看代码
css
/* HnuiIcon */
@import url('./icon.css');

/* 全局css变量 */

:root {
  /* #region vars-BrandColor */

  /* 品牌色 */
  --hnui-brand-color: #1f78ff;
  --hnui-brand-color1: #005ce5;
  --hnui-brand-color2: #4b83f7;
  --hnui-brand-color3: #3384ff;
  --hnui-brand-color4: #599bff;
  --hnui-brand-color5: #80b2ff;
  --hnui-brand-color6: #b2d1ff;
  --hnui-brand-color7: #cce0ff;
  --hnui-brand-color8: #d9e8ff;
  --hnui-brand-color9: #ebf3ff;
  --hnui-brand-color10: #f2f7ff;

  /* 状态颜色 */
  --hnui-brand-color-normal: var(--hnui-brand-color);
  --hnui-brand-color-hover: var(--hnui-brand-color3);
  --hnui-brand-color-active: var(--hnui-brand-color1);
  --hnui-brand-color-elected: var(--hnui-brand-color9);
  --hnui-brand-color-disabled: var(--hnui-brand-color7);

  --hnui-brand-color-disabled-border: var(--hnui-brand-color6);
  --hnui-brand-color-disabled-bg: var(--hnui-brand-color10);

  /* #endregion vars-BrandColor */

  /* #region vars-DomainColor */

  /* 功能色 */
  --hnui-success-color: #2ba471;
  --hnui-success-hover-color: #51bd90;
  --hnui-success-active-color: #2ba471;

  --hnui-warning-color: #faad14;
  --hnui-warning-hover-color: #ffc146;
  --hnui-warning-active-color: #db960b;

  --hnui-danger-color: #ff4d4d;
  --hnui-danger-hover-color: #ff7676;
  --hnui-danger-active-color: #d82a2a;

  --hnui-info-color: #8d97a6;
  --hnui-info-hover-color: #b2b7c0;
  --hnui-info-active-color: #707a89;

  /* #endregion vars-DomainColor */

  /* #region vars-NeutralColor */
  /* 中性色 */

  /* 标题文字 */
  --hnui-neutral-color-1: #2d2e33;

  /* 正文、重要信息文字、按钮文字、输入框正常状态文字等 */
  --hnui-neutral-color-2: #43454d;

  /* 内容文字、说明叙述文字 */
  --hnui-neutral-color-3: #5d606a;

  /* 提示性文字、辅助文本 */
  --hnui-neutral-color-4: #9298a0;

  /* 禁用文本 */
  --hnui-neutral-color-5: #b7bdc7;

  /* 描边格描边、输入文本描边、下拉选择描边等 */
  --hnui-neutral-color-6: #dfe2e7;

  /* 全局背景、禁用状态、分割线等 */
  --hnui-neutral-color-7: #f0f2f5;

  /* 最低颜色等 */
  --hnui-neutral-color-8: #fafbfc;

  /* #endregion vars-NeutralColor */

  /* 默认文字颜色 */
  --hnui-text-color: var(--hnui-neutral-color-2);

  --hnui-info-bg: #eceff4;
  --hnui-info-bg2: #e7e7e7;

  --hnui-color-white: #fff;
  --hnui-color-black: #000;

  --hnui-border-width: 1px;
  --hnui-border-dashed-width: 2px;
  --hnui-border-color: var(--hnui-neutral-color-6);

  --hnui-disabled-bg-color: var(--hnui-neutral-color-7);
  --hnui-disabled-color: var(--hnui-neutral-color-5);

  --hnui-control-height: 32px;
  --hnui-control-height-sm: 24px;
  --hnui-control-height-lg: 40px;

  --hnui-control-transparent: transparent;
}

.dark {
  --hnui-brand-color6: rgb(50, 59, 74);
  --hnui-brand-color7: #30353d;
  --hnui-brand-color8: #42484f;
  --hnui-brand-color9: #4c4f54;
  --hnui-brand-color10: #29292c;

  /* 中性色 */

  /* 标题文字 */
  --hnui-neutral-color-1: #c4c8dc;

  /* 正文、重要信息文字、按钮文字、输入框正常状态文字等 */
  --hnui-neutral-color-2: #b9bed3;

  /* 内容文字、说明叙述文字 */
  --hnui-neutral-color-3: #afb5c7;

  /* 提示性文字、辅助文本 */
  --hnui-neutral-color-4: #6a6f75;

  /* 禁用文本 */
  --hnui-neutral-color-5: #53565b;

  /* 描边格描边、输入文本描边、下拉选择描边等 */
  --hnui-neutral-color-6: #59595a;

  /* 全局背景、禁用状态、分割线等 */
  --hnui-neutral-color-7: #343535;

  /* 最低颜色等 */
  --hnui-neutral-color-8: #151516;

  --hnui-info-bg: #393b3d;

  --hnui-color-white: #000;
  --hnui-color-black: #fff;

  --hnui-control-transparent: transparent;
}

*,
::before,
::after {
  box-sizing: border-box;
}

body {
  color: var(--hnui-neutral-color-2);
}

/* #region vars-FontFamily */
@font-face {
  font-family: 'DIN';
  src: url('/fonts/D-DIN-PRO/D-DIN-PRO.woff2') format('woff2'),
    url('/fonts/D-DIN-PRO/D-DIN-PRO.woff') format('woff'),
    url('/fonts/D-DIN-PRO/D-DIN-PRO.ttf') format('truetype'),
    url('/fonts/D-DIN-PRO/D-DIN-PRO.eot') format('embedded-opentype'),
    url('/fonts/D-DIN-PRO/D-DIN-PRO.svg') format('svg');
  font-weight: normal;
  font-style: normal;
}

/* 使用数字字体 */
.use-font {
  font-family: DIN;
}
/* #endregion vars-FontFamily */

/* #region vars-BoxShadow */
/* 一级投影(默认向下) */
.hnui-box-shadow {
  box-shadow: 0px 6px 16px 6px rgba(0, 0, 0, 0.18);
}
.hnui-box-shadow.hover,
.hnui-box-shadow:hover {
  box-shadow: 0px 6px 16px 6px rgba(31, 120, 255, 0.18);
}

/* 二级投影(默认向下) */
.hnui-box-shadow_l2 {
  box-shadow: 0px 6px 16px 6px rgba(0, 0, 0, 0.12);
}
.hnui-box-shadow_l2.hover,
.hnui-box-shadow_l2:hover {
  box-shadow: 0px 6px 16px 6px rgba(31, 120, 255, 0.12);
}

/* 三级投影(默认向下) */
.hnui-box-shadow_l3 {
  box-shadow: 0px 6px 16px 6px rgba(0, 0, 0, 0.06);
}
.hnui-box-shadow_l3.hover,
.hnui-box-shadow_l3:hover {
  box-shadow: 0px 6px 16px 6px rgba(31, 120, 255, 0.06);
}
/* #endregion vars-BoxShadow */
css

/* === 基础轮播容器 === */
.hnui-swiper {
  --hnui-swiper-height: 400px;
  --hnui-swiper-bg: #3c3c3c;
  --hnui-swiper-arrow-bg: transparent;
  --hnui-swiper-arrow-hover-bg: transparent;
  --hnui-swiper-arrow-color: #ffffff;
  --hnui-swiper-pagination-bg: rgba(255, 255, 255, 0.3);
  --hnui-swiper-pagination-active-bg: #ffffff;

  position: relative;
  width: 100%;
  height: var(--hnui-swiper-height);
  overflow: hidden;
  border-radius: 6px;
}

/* === 轮播包装器 === */
.hnui-swiper-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  transition: transform 0.3s ease;
}

/* === 轮播项 === */
.hnui-swiper-slide {
  flex-shrink: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--hnui-swiper-bg);
  color: #ffffff;
  font-size: 48px;
  font-weight: bold;
}

/* === 切换箭头 === */
.hnui-swiper-button-prev,
.hnui-swiper-button-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--hnui-swiper-arrow-bg);
  border: none;
  border-radius: 50%;
  color: var(--hnui-swiper-arrow-color);
  font-size: 20px;
  cursor: pointer;
  transition: background 0.3s;
  z-index: 10;
  user-select: none;
}

.hnui-swiper-button-prev:hover,
.hnui-swiper-button-next:hover {
  background: var(--hnui-swiper-arrow-hover-bg);
}

.hnui-swiper-button-prev {
  left: 16px;
}

.hnui-swiper-button-next {
  right: 16px;
}

.hnui-swiper-button-prev::before {
  content: "‹";
}

.hnui-swiper-button-next::before {
  content: "›";
}

/* === 分页器(条状指示器) === */
.hnui-swiper-pagination {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  z-index: 10;
}

.hnui-swiper-pagination-bullet {
  width: 32px;
  height: 4px;
  background: var(--hnui-swiper-pagination-bg);
  border-radius: 2px;
  cursor: pointer;
  transition: all 0.3s;
}

.hnui-swiper-pagination-bullet-active {
  background: var(--hnui-swiper-pagination-active-bg);
}

/* === 卡片式轮播 === */
.hnui-swiper-cards {
  --hnui-swiper-pagination-bg: rgba(0, 0, 0, 0.2);
  --hnui-swiper-pagination-active-bg: rgba(0, 0, 0, 0.6);
  --hnui-swiper-arrow-color: #666666;

  perspective: 1200px;
}

.hnui-swiper-cards .hnui-swiper-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
}

.hnui-swiper-cards .hnui-swiper-slide {
  position: absolute;
  width: 30%;
  aspect-ratio: 1 / 1;
  height: auto;
  transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 8px;
}

/* 卡片位置 - 左侧 */
.hnui-swiper-cards .hnui-swiper-slide-prev {
  left: 2%;
  transform: translateX(0) scale(0.85);
  opacity: 0.6;
  z-index: 1;
}

/* 卡片位置 - 当前激活 */
.hnui-swiper-cards .hnui-swiper-slide-active {
  left: 50%;
  transform: translateX(-50%) scale(1);
  opacity: 1;
  z-index: 2;
}

/* 卡片位置 - 右侧 */
.hnui-swiper-cards .hnui-swiper-slide-next {
  right: 2%;
  left: auto;
  transform: translateX(0) scale(0.85);
  opacity: 0.6;
  z-index: 1;
}

/* 隐藏其他卡片 */
.hnui-swiper-cards .hnui-swiper-slide:not(.hnui-swiper-slide-prev):not(.hnui-swiper-slide-active):not(.hnui-swiper-slide-next) {
  opacity: 0;
  transform: scale(0.5);
}

/* === 暗色模式适配 === */
.dark .hnui-swiper {
  --hnui-swiper-bg: #2c2c2c;
  --hnui-swiper-arrow-bg: transparent;
  --hnui-swiper-arrow-hover-bg: transparent;
  --hnui-swiper-pagination-bg: rgba(255, 255, 255, 0.2);
}
js
~(function () {
  'use strict';

  const ACTIVE_BULLET_CLASS = 'hnui-swiper-pagination-bullet-active';
  const SLIDE_ACTIVE_CLASS = 'hnui-swiper-slide-active';
  const SLIDE_PREV_CLASS = 'hnui-swiper-slide-prev';
  const SLIDE_NEXT_CLASS = 'hnui-swiper-slide-next';
  const INITIALIZED_FLAG = 'data-swiper-initialized';

  /**
   * 初始化轮播组件
   * @param {HTMLElement} swiper - 轮播容器元素
   */
  function initSwiper(swiper) {
    // 防止重复初始化
    if (swiper.hasAttribute(INITIALIZED_FLAG)) return;
    swiper.setAttribute(INITIALIZED_FLAG, 'true');

    const wrapper = swiper.querySelector('.hnui-swiper-wrapper');
    const slides = swiper.querySelectorAll('.hnui-swiper-slide');
    const prevBtn = swiper.querySelector('.hnui-swiper-button-prev');
    const nextBtn = swiper.querySelector('.hnui-swiper-button-next');
    const bullets = swiper.querySelectorAll('.hnui-swiper-pagination-bullet');
    const isCardsMode = swiper.classList.contains('hnui-swiper-cards');

    if (!wrapper || slides.length === 0) return;

    let currentIndex = 0;
    let timer = null;
    const slideCount = slides.length;

    // 更新轮播状态
    function updateSlider() {
      if (isCardsMode) {
        // 卡片模式 - 更新 slide 类名
        const prevIndex = currentIndex === 0 ? slideCount - 1 : currentIndex - 1;
        const nextIndex = (currentIndex + 1) % slideCount;

        slides.forEach((slide, index) => {
          slide.classList.remove(SLIDE_ACTIVE_CLASS, SLIDE_PREV_CLASS, SLIDE_NEXT_CLASS);
          if (index === currentIndex) {
            slide.classList.add(SLIDE_ACTIVE_CLASS);
          } else if (index === prevIndex) {
            slide.classList.add(SLIDE_PREV_CLASS);
          } else if (index === nextIndex) {
            slide.classList.add(SLIDE_NEXT_CLASS);
          }
        });
      } else {
        // 普通模式 - 更新 transform
        wrapper.style.transform = `translateX(-${currentIndex * 100}%)`;
      }

      // 更新分页指示器
      bullets.forEach((bullet, index) => {
        bullet.classList.toggle(ACTIVE_BULLET_CLASS, index === currentIndex);
      });
    }

    // 切换到指定 slide
    function goToSlide(index) {
      currentIndex = index;
      updateSlider();
    }

    // 上一张
    function prevSlide() {
      currentIndex = currentIndex === 0 ? slideCount - 1 : currentIndex - 1;
      updateSlider();
    }

    // 下一张
    function nextSlide() {
      currentIndex = (currentIndex + 1) % slideCount;
      updateSlider();
    }

    // 自动播放
    function startAutoPlay() {
      stopAutoPlay();
      timer = setInterval(nextSlide, 3000);
    }

    function stopAutoPlay() {
      if (timer) {
        clearInterval(timer);
        timer = null;
      }
    }

    // 绑定事件
    if (prevBtn) {
      prevBtn.addEventListener('click', prevSlide);
    }

    if (nextBtn) {
      nextBtn.addEventListener('click', nextSlide);
    }

    bullets.forEach((bullet, index) => {
      bullet.addEventListener('click', () => goToSlide(index));
    });

    // 鼠标悬停暂停自动播放
    swiper.addEventListener('mouseenter', stopAutoPlay);
    swiper.addEventListener('mouseleave', startAutoPlay);

    // 初始化状态
    updateSlider();
    startAutoPlay();
  }

  // 初始化所有未初始化的轮播组件
  function initAllSwipers() {
    const swipers = document.querySelectorAll('.hnui-swiper:not([' + INITIALIZED_FLAG + '])');
    swipers.forEach(initSwiper);
  }

  // DOM 加载完成后初始化
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initAllSwipers);
  } else {
    initAllSwipers();
  }

  // 使用 MutationObserver 监听 DOM 变化,自动初始化动态添加的组件
  const observer = new MutationObserver((mutations) => {
    let shouldInit = false;
    for (const mutation of mutations) {
      if (mutation.addedNodes.length) {
        shouldInit = true;
        break;
      }
    }
    if (shouldInit) {
      initAllSwipers();
    }
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true
  });

  // 暴露全局方法
  window.HnuiSwiper = {
    init: initSwiper,
    initAll: initAllSwipers
  };
})();

仅幻灯片 Slides Only

最基础的轮播效果,仅展示幻灯片内容,支持自动播放和鼠标悬停暂停。

1
2
3
4
5
查看代码
vue
<div class="hnui-swiper">
  <div class="hnui-swiper-wrapper">
    <div class="hnui-swiper-slide">1</div>
    <div class="hnui-swiper-slide">2</div>
    <div class="hnui-swiper-slide">3</div>
    <div class="hnui-swiper-slide">4</div>
    <div class="hnui-swiper-slide">5</div>
  </div>
</div>

指示器&切换箭头 条状(bars)

带有分页指示器和左右切换箭头的轮播效果,用户可以通过箭头或指示器控制切换。

1
2
3
4
5
查看代码
vue
<div class="hnui-swiper">
  <div class="hnui-swiper-wrapper">
    <div class="hnui-swiper-slide">1</div>
    <div class="hnui-swiper-slide">2</div>
    <div class="hnui-swiper-slide">3</div>
    <div class="hnui-swiper-slide">4</div>
    <div class="hnui-swiper-slide">5</div>
  </div>

  <!-- 切换箭头 -->
  <button class="hnui-swiper-button-prev"></button>
  <button class="hnui-swiper-button-next"></button>

  <!-- 分页指示器 -->
  <div class="hnui-swiper-pagination">
    <span class="hnui-swiper-pagination-bullet hnui-swiper-pagination-bullet-active"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
  </div>
</div>

卡片式 Cards

卡片式轮播效果,当前卡片居中显示,前后卡片以缩小和半透明的形式显示,提供更好的视觉层次感。

5
1
2
3
4
查看代码
vue
<div class="hnui-swiper hnui-swiper-cards">
  <div class="hnui-swiper-wrapper">
    <div class="hnui-swiper-slide hnui-swiper-slide-prev">5</div>
    <div class="hnui-swiper-slide hnui-swiper-slide-active">1</div>
    <div class="hnui-swiper-slide hnui-swiper-slide-next">2</div>
    <div class="hnui-swiper-slide">3</div>
    <div class="hnui-swiper-slide">4</div>
  </div>

  <!-- 切换箭头 -->
  <button class="hnui-swiper-button-prev"></button>
  <button class="hnui-swiper-button-next"></button>

  <!-- 分页指示器 -->
  <div class="hnui-swiper-pagination">
    <span class="hnui-swiper-pagination-bullet hnui-swiper-pagination-bullet-active"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
    <span class="hnui-swiper-pagination-bullet"></span>
  </div>
</div>

统一UI