使用swiper库写水平轮播图,带过渡效果

Lirioing2026/06/09WebFEWeb

前言

之前是使用element-plus的el-carousel组件改造的官网轮播图。由于element-plus没有滑动切换轮播图的方法,本可以手写,但考虑多学一点,就使用了swiper组件。

关联功能页面

响应式宽高计算工具函数Vue3组合式函数监听窗口大小变化

安装方法

npm install swiper  // 安装的版本是12.2.0

轮播图组件

<template>
  <div class="swiper-horizontal">
    <swiper 
      style="height:100vh"
      :modules="modules"
      effect="slide"
      :speed="700"
      :loop="true"
      :autoplay="{
        delay: 3000,                  // 切换间隔(毫秒),默认 3000
        disableOnInteraction: false,  // 是否在用户交互后禁用自动播放,默认 false
      }"
      :navigation="{
        nextEl: '.my-next-btn',
        prevEl: '.my-prev-btn'
      }"
      :pagination="{
        clickable: true,
        el: '.custom-pagination'    // 自定义容器
      }"
      @swiper="setSwiper"
      @slideChange="onSlideChange"
    >
      <swiper-slide v-for="(item, index) in productList" :key="index">
        <div class="swiper-item">
          <img :src="item.img" alt="">
        </div>
      </swiper-slide>
    </swiper>
    <div class="custom-pagination"></div>
    <div 
      :style="{
        width: indicatorWidth + 'px',
        height: indicatorHeight + 'px',
        left: indicatorLeft + 'px'
      }"
      class="swiper-indicators">
      <div style="padding-top: 0.6vw;">
        <div 
          class="swiper-indicator-arrow iconfont icon-shangjiantou my-prev-btn" 
          >
        </div>
      </div>
      <div 
        :style="{ width: miniImageWidth + 'px', height: miniImageWidth + 'px' }"
        class="swiper-indicator-item"
        :class="{'active': miniIndex === activeIndex}"
        v-for="(item, miniIndex) in productList" 
        :key="miniIndex" 
        @click="carouselRef.setActiveItem(miniIndex)">
          <img  :src="item.miniImg" alt="">
      </div>
      <div style="padding-bottom: 0.6vw;">
        <div 
          class="swiper-indicator-arrow iconfont icon-xiajiantou my-next-btn" 
          >
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { calculateWidth, calculateHeight } from '@/utils/dimensionCalculator'
import { useWindowResize } from '@/composables/useWindowResize'
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';

// import Swiper core and required modules
import { Autoplay, Pagination, Navigation } from 'swiper/modules'

// 注册模块
const modules = [Autoplay, Pagination, Navigation]

// Import Swiper styles
import 'swiper/css';
// 引入功能模块样式(用了哪个功能就要引入对应的样式)
import 'swiper/css/navigation'
import 'swiper/css/pagination'




const swiperInstance = ref(null)
const activeIndex = ref(0)

const setSwiper = (swiper) => {
  // 自定义缓动函数:cubic-bezier(0.22, 0.61, 0.36, 1)
  swiper.wrapperEl.style.transitionTimingFunction = 'cubic-bezier(0.42, 0.0, 0.58, 1.0)'
}

// 切换
const onSlideChange = (swiper) => {
  activeIndex.value = swiper.realIndex
}

const productList = [
  {
    img: new URL('@/static/images/product/banner/product1.jpg', import.meta.url).href,
    miniImg: new URL('@/static/images/product/banner/product1-mini.png', import.meta.url).href,
  },
  {
    img: new URL('@/static/images/product/banner/product2.jpg', import.meta.url).href,
    miniImg: new URL('@/static/images/product/banner/product2-mini.png', import.meta.url).href,
  },
  {
    img: new URL('@/static/images/product/banner/product3.jpg', import.meta.url).href,
    miniImg: new URL('@/static/images/product/banner/product3-mini.png', import.meta.url).href,
  },
  {
    img: new URL('@/static/images/product/banner/product4.jpg', import.meta.url).href,
    miniImg: new URL('@/static/images/product/banner/product4-mini.png', import.meta.url).href,
  },
  {
    img: new URL('@/static/images/product/banner/product5.jpg', import.meta.url).href,
    miniImg: new URL('@/static/images/product/banner/product5-mini.png', import.meta.url).href,
  },
]

// 使用窗口大小监听组合式函数
useWindowResize(() => {
  calculateDimensions()
})

const resizeFlag = ref(0)
const indicatorWidth = ref(70)
const indicatorHeight = ref(498)
const indicatorLeft = ref(92)
const miniImageWidth = computed(() => {
  return indicatorWidth.value > 24 ? indicatorWidth.value - 6 : 18
})
// 计算轮播图高度和小图片尺寸
const calculateDimensions = () => {
  const windowWidth = window.innerWidth
  // 1920宽度下高度710,按比例计算
  indicatorWidth.value = calculateWidth(70, windowWidth)
  indicatorHeight.value = calculateHeight(1920, 498, windowWidth)
  indicatorLeft.value = calculateWidth(92, windowWidth)
  resizeFlag.value++
}
</script>
<style scoped>
.swiper-horizontal {
  position: relative;
  /* 禁止拖拽图片(针对 img 标签) */
  user-select: none;
}
.swiper-item {
  height: 100%;
}
.swiper-item img {
  width: 100%;              /* 宽度占满 */
  height: 100%;             /* 高度自适应 */
  max-height: 100%;         /* 不超出容器高度 */
  display: block;

}
.swiper-indicators {
  position: absolute;
  top: 50%;
  z-index: 10;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  flex-direction: column;
  gap: 1vw;
  background: rgba(25, 52, 11, 0.8);
  border-radius: 50px;
  min-width: 24px;
  min-height: 172px;
}
.swiper-indicator-item {
  display: flex;
  border-radius: 50%;
  cursor: pointer;
}
.swiper-indicator-item:hover {
  transform: scale(1.01);
  box-shadow: 0 0 10px #fff;
}
.swiper-indicator-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.swiper-indicator-item.active {
  box-shadow: 0 0 10px #fff;
}
.swiper-indicator-arrow {
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(20px, 1.25vw, 24px);
  color: #fff;
  cursor: pointer;
}

.custom-pagination {
  display: none;
}

/* 或者直接覆盖 */
:deep(.swiper-pagination-bullet-active) {
  background: #a7ca25 !important;
}

@media screen and (max-width: 1200px) {
  .swiper-horizontal {
    margin-top: 80px;
  }
}
@media (max-width: 480px) {
  .swiper-horizontal {
    margin-top: 70px;
  }
  .custom-pagination {
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
  }
  .swiper-indicators {
    display: none;
  }
}
</style>

运行效果

上次更新 2026/6/9 17:35:15