Vue3使用触摸滑动插件(Swiper)

简介: 本文介绍如何在Vue2项目中使用Swiper插件实现触摸滑动功能,并封装了两种轮播图展示形式:首页轮播图(`type: banner`)和走马灯轮播图(`type: carousel`),以及信息展播模式(`type: broadcast`)。支持自定义轮播图片、区域尺寸、动画效果等属性。通过示例代码展示了不同切换动画及自定义效果,并提供了在线预览。适用于多种应用场景,提升用户体验。

参考文档:

本文使用版本:Swiper@11.1.7

安装插件:pnpm add swiper

本文基于Swiper插件进行封装,主要实现两种形式的轮播图展示:

  • 首页轮播图切换展示(type: banner)

  • 走马灯轮播图滚动展示(type: carousel)

  • 信息展播模式(type: broadcast)

可自定义设置以下属性:

  • 轮播图片数组(images),类型:Array<{title?: string, link?: string, src: string}>,默认 []

  • 轮播区域宽度(width),类型:number | string,单位 px,默认 '100%'

  • 轮播区域高度(height),类型:number | string,单位 px,默认 '100%'

  • 滑动轮播模式(mode),类型:'banner' | 'carousel' | 'broadcast',默认 'banner';banner:轮播图模式;carousel:走马灯模式;broadcast:信息展播模式

  • 是否显示导航(navigation),类型:boolean,默认 false

  • 切换动画效果(effect),类型:'slide' | 'fade' | 'cube' | 'flip' | 'coverflow' | 'cards' | 'creative',默认 'slide'

  • 自动切换的时间间隔(delay),仅当 mode:'banner' 时生效,单位 ms,类型:number,默认 3000

  • 切换过渡的动画持续时间(speed),单位 ms,类型:number,默认 300

  • 是否循环切换(loop),类型:boolean,默认 true

  • 当鼠标移入走马灯时,是否暂停自动轮播(pauseOnMouseEnter),仅当 mode: 'banner' 或 mode: 'carousel' 时生效,类型:boolean,默认 false

  • 是否可以鼠标拖动(swipe),类型:boolean,默认 true

  • 预加载时的 loading 颜色(preloaderColor),类型:'theme' | 'white' | 'black',默认 'theme'

效果如下图:在线预览

①创建触摸滑动组件Swiper.vue:

<script setup lang="ts">
import { ref, computed } from 'vue'
import type { Swiper as SwiperTypes } from 'swiper/types'
import { Swiper, SwiperSlide } from 'swiper/vue'
import {
  Pagination,
  Navigation,
  Autoplay,
  EffectFade,
  EffectCube,
  EffectFlip,
  EffectCoverflow,
  EffectCards,
  EffectCreative,
  Mousewheel
} from 'swiper/modules'
import 'swiper/less'
import 'swiper/less/navigation'
import 'swiper/less/pagination'
import 'swiper/less/effect-fade'
import 'swiper/less/effect-cube'
import 'swiper/less/effect-flip'
import 'swiper/less/effect-coverflow'
import 'swiper/less/effect-cards'
import 'swiper/less/effect-creative'
interface Image {
  name?: string // 图片名称
  src: string // 图片地址
  link?: string // 图片跳转链接
}
interface Props {
  images?: Image[] // 轮播图片数组
  width?: number | string // 轮播区域宽度,单位 px
  height?: number | string // 轮播区域高度,单位 px
  mode?: 'banner' | 'carousel' | 'broadcast' // banner: 轮播图模式; carousel: 走马灯模式; broadcast: 信息展播模式
  navigation?: boolean // 是否显示导航
  effect?: 'slide' | 'fade' | 'cube' | 'flip' | 'coverflow' | 'cards' | 'creative' // 切换动画效果
  delay?: number // 自动切换的时间间隔,仅当 mode: 'banner' 时生效,单位 ms
  speed?: number // 切换过渡的动画持续时间,单位 ms
  loop?: boolean // 是否循环切换
  pauseOnMouseEnter?: boolean // 当鼠标移入走马灯时,是否暂停自动轮播,仅当 mode: 'banner' 或 mode: 'carousel' 时生效
  swipe?: boolean // 是否可以鼠标拖动
  preloaderColor?: 'theme' | 'white' | 'black' // 预加载时的 loading 颜色
}
const props = withDefaults(defineProps<Props>(), {
  images: () => [],
  width: '100%',
  height: '100%',
  mode: 'banner',
  navigation: false,
  effect: 'slide',
  delay: 3000,
  speed: 300,
  loop: true,
  pauseOnMouseEnter: false,
  swipe: true,
  preloaderColor: 'theme'
})
const swiperWidth = computed(() => {
  if (typeof props.width === 'number') {
    return props.width + 'px'
  } else {
    return props.width
  }
})
const swiperHeight = computed(() => {
  if (typeof props.height === 'number') {
    return props.height + 'px'
  } else {
    return props.height
  }
})
const modulesBanner = computed(() => {
  const modules = [Navigation, Pagination, Autoplay]
  const effectMoudles = {
    fade: EffectFade,
    cube: EffectCube,
    flip: EffectFlip,
    coverflow: EffectCoverflow,
    cards: EffectCards,
    creative: EffectCreative
  }
  if (props.effect !== 'slide') {
    modules.push(effectMoudles[props.effect])
  }
  return modules
})
const autoplayBanner = ref({
  delay: props.delay,
  disableOnInteraction: false, // 用户操作 swiper 之后,是否禁止 autoplay。默认为 true:停止。
  pauseOnMouseEnter: props.pauseOnMouseEnter // 鼠标置于 swiper 时暂停自动切换,鼠标离开时恢复自动切换,默认 false
})
const modulesCarousel = ref([Autoplay])
const autoplayCarousel = ref<object | boolean>({
  delay: 0,
  disableOnInteraction: false
})
const modulesBroadcast = ref([Navigation, Pagination, Mousewheel])
const emits = defineEmits(['swiper', 'change'])
function onSwiper(swiper: SwiperTypes) {
  emits('swiper', swiper)
  if (props.mode === 'carousel' && props.pauseOnMouseEnter) {
    swiper.el.onmouseenter = () => {
      // 移入暂停
      swiper.autoplay.stop()
    }
    swiper.el.onmouseleave = () => {
      // 移出启动
      swiper.autoplay.start()
    }
  }
}
function getImageName(image: Image) {
  // 从图片地址 src 中获取图片名称
  if (image.name) {
    return image.name
  } else {
    const res = image.src.split('?')[0].split('/')
    return res[res.length - 1]
  }
}
</script>
<template>
  <Swiper
    v-if="mode === 'banner'"
    :class="{ 'swiper-no-swiping': !swipe }"
    :style="`width: ${swiperWidth}; height: ${swiperHeight};`"
    :modules="modulesBanner"
    :navigation="navigation"
    :slides-per-view="1"
    :autoplay="autoplayBanner"
    :effect="effect"
    :speed="speed"
    :loop="loop"
    lazy
    @swiper="onSwiper"
    @slideChange="(swiper) => $emit('change', swiper)"
    v-bind="$attrs"
  >
    <SwiperSlide v-for="(image, index) in images" :key="index">
      <a class="image-link" :href="image.link ? image.link : 'javascript:;'" :target="image.link ? '_blank' : '_self'">
        <img class="u-image" :src="image.src" :alt="getImageName(image)" loading="lazy" />
      </a>
      <div :class="`swiper-lazy-preloader swiper-lazy-preloader-${preloaderColor}`"></div>
    </SwiperSlide>
  </Swiper>
  <Swiper
    v-if="mode === 'carousel'"
    class="swiper-no-swiping"
    :style="`width: ${swiperWidth}; height: ${swiperHeight};`"
    :modules="modulesCarousel"
    :autoplay="autoplayCarousel"
    :speed="speed"
    :loop="loop"
    lazy
    @swiper="onSwiper"
    @slideChange="(swiper) => $emit('change', swiper)"
    v-bind="$attrs"
  >
    <SwiperSlide v-for="(image, index) in images" :key="index">
      <a class="image-link" :href="image.link ? image.link : 'javascript:;'" :target="image.link ? '_blank' : '_self'">
        <img class="u-image" :src="image.src" :alt="getImageName(image)" loading="lazy" />
      </a>
      <div :class="`swiper-lazy-preloader swiper-lazy-preloader-${preloaderColor}`"></div>
    </SwiperSlide>
  </Swiper>
  <Swiper
    v-if="mode === 'broadcast'"
    :style="`width: ${swiperWidth}; height: ${swiperHeight};`"
    :modules="modulesBroadcast"
    :navigation="navigation"
    :speed="speed"
    :loop="loop"
    lazy
    @swiper="onSwiper"
    @slideChange="(swiper) => $emit('change', swiper)"
    v-bind="$attrs"
  >
    <SwiperSlide v-for="(image, index) in images" :key="index">
      <a :href="image.link ? image.link : 'javascript:;'" :target="image.link ? '_blank' : '_self'" class="image-link">
        <img class="u-image" :src="image.src" :alt="getImageName(image)" loading="lazy" />
      </a>
      <div :class="`swiper-lazy-preloader swiper-lazy-preloader-${preloaderColor}`"></div>
    </SwiperSlide>
  </Swiper>
</template>
<style lang="less" scoped>
.image-link {
  display: block;
  height: 100%;
  .u-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    cursor: pointer;
  }
}
.swiper {
  --swiper-theme-color: @themeColor;
}
:deep(.swiper-wrapper) {
  // 自动切换过渡效果设置
  transition-timing-function: linear; // 线性过渡模拟走马灯效果
  -webkit-transition-timing-function: linear;
}
:deep(.swiper-pagination-bullet) {
  width: 12px;
  height: 12px;
}
.swiper-lazy-preloader-theme {
  --swiper-preloader-color: @themeColor;
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import Swiper from './Swiper.vue'
import pkg from '/package.json'
import { ref, shallowReactive, onBeforeMount } from 'vue'

const images = ref<any[]>([])
function loadImages() {
  for (let i = 1; i <= 6; i++) {
    images.value.push({
      title: `image-${i}`,
      link: `https://cdn.jsdelivr.net/gh/themusecatcher/resources@0.0.6/${i}.jpg`,
      src: `https://cdn.jsdelivr.net/gh/themusecatcher/resources@0.0.6/${i}.jpg`
    })
  }
}
onBeforeMount(() => {
  // 组件已完成响应式状态设置,但未创建DOM节点
  loadImages()
})
function onChange(swiper: any) {
  console.log('slider change', swiper)
}
const effects = ['slide', 'fade', 'cube', 'flip', 'coverflow', 'cards']
const creativeEffects = [
  {
    prev: {
      shadow: true,
      translate: [0, 0, -400]
    },
    next: {
      translate: ['100%', 0, 0]
    }
  },
  {
    prev: {
      shadow: true,
      translate: ['-120%', 0, -500]
    },
    next: {
      shadow: true,
      translate: ['120%', 0, -500]
    }
  },
  {
    prev: {
      shadow: true,
      translate: ['-20%', 0, -1]
    },
    next: {
      translate: ['100%', 0, 0]
    }
  },
  {
    prev: {
      shadow: true,
      translate: [0, 0, -800],
      rotate: [180, 0, 0]
    },
    next: {
      shadow: true,
      translate: [0, 0, -800],
      rotate: [-180, 0, 0]
    }
  },
  {
    prev: {
      shadow: true,
      translate: ['-125%', 0, -800],
      rotate: [0, 0, -90]
    },
    next: {
      shadow: true,
      translate: ['125%', 0, -800],
      rotate: [0, 0, 90]
    }
  },
  {
    prev: {
      shadow: true,
      origin: 'left center',
      translate: ['-5%', 0, -200],
      rotate: [0, 100, 0]
    },
    next: {
      origin: 'right center',
      translate: ['5%', 0, -200],
      rotate: [0, -100, 0]
    }
  }
]
const navigation = shallowReactive<{ [key: string]: any }>({})
function onBroadcastSwiper(swiper: any) {
  console.log('carousel', swiper)
  navigation.prevEl = swiper.navigation.prevEl
  navigation.prevEl.style.display = 'none'
  navigation.nextEl = swiper.navigation.nextEl
  navigation.nextEl.style.display = 'none'
}
function onPrev() {
  navigation.prevEl.click()
}
function onNext() {
  navigation.nextEl.click()
}
</script>
<template>
  <div>
    <h1>Swiper 参考文档</h1>
    <ul class="m-list">
      <li>
        <a class="u-file" href="https://swiperjs.com/" target="_blank">Swiper官方</a>
      </li>
      <li>
        <a class="u-file" href="https://swiperjs.com/swiper-api" target="_blank">Swiper API</a>
      </li>
      <li>
        <a class="u-file" href="https://swiperjs.com/vue" target="_blank">Swiper Vue</a>
      </li>
      <li>
        <a class="u-file" href="https://swiperjs.com/demos" target="_blank">Swiper Demos</a>
      </li>
    </ul>
    <Space class="mt30" gap="small">
      <h1>Swiper</h1>
      <Tag color="volcano">{
  
  { pkg.dependencies.swiper }}</Tag>
    </Space>
    <h2 class="mt30 mb10">基本使用</h2>
    <Swiper
      :images="images"
      :height="640"
      :pagination="{
        dynamicBullets: true,
        clickable: true
      }"
      @change="onChange"
    />
    <h2 class="mt30 mb10">各种切换动画</h2>
    <Flex :gap="48" wrap="wrap">
      <Badge style="width: 30%" :value="effect" color="volcano" v-for="(effect, index) in effects" :key="index">
        <Swiper
          style="display: inline-block"
          :images="images"
          :height="240"
          :pagination="{
            dynamicBullets: true,
            clickable: true
          }"
          :effect="effect"
        />
      </Badge>
    </Flex>
    <h2 class="mt30 mb10">自定义切换动画</h2>
    <Flex :gap="48" wrap="wrap">
      <Badge
        style="width: 30%"
        value="creative"
        color="cyan"
        v-for="(creativeEffect, index) in creativeEffects"
        :key="index"
      >
        <Swiper
          style="display: inline-block"
          :images="images"
          :height="240"
          :pagination="{
            dynamicBullets: true,
            clickable: true
          }"
          effect="creative"
          :creativeEffect="creativeEffect"
        />
      </Badge>
    </Flex>
    <h2 class="mt30 mb10">走马灯</h2>
    <Swiper :images="images" mode="carousel" :height="240" :slides-per-view="3" :space-between="20" :speed="2500" />
    <h2 class="mt30 mb10">信息展播</h2>
    <Flex vertical gap="middle">
      <Space>
        <Button type="primary" @click="onPrev">Prev</Button>
        <Button type="primary" @click="onNext">Next</Button>
      </Space>
      <Swiper
        :images="images"
        mode="broadcast"
        :pagination="{
          dynamicBullets: true,
          clickable: true
        }"
        :height="320"
        :slides-per-view="3"
        :space-between="30"
        navigation
        mousewheel
        @swiper="onBroadcastSwiper"
      />
    </Flex>
  </div>
</template>
相关文章
|
18天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
15天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
35 7
|
16天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
38 3
|
15天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
36 1
|
15天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
36 1
|
17天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
18天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
5天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
5天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
5天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。