参考文档:
本文使用版本: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>