Vue实现轮播图组件的封装

Vue
341
0
0
2023-05-11
标签   Vue组件
目录
  • 轮播图功能-获取数据
  • 轮播图-通用轮播图组件
  • 轮播图-数据渲染
  • 轮播图-逻辑封装

轮播图功能-获取数据

目标: 基于pinia获取轮播图数据

核心代码:

(1)在types/data.d.ts文件中定义轮播图数据的类型声明

// 所有接口的通用类型
export type ApiRes <T> = {
code: string,
msg: string,
result: T
}
// 轮播图类型
export type BannerItem = {
hrefUrl: string
id: string
imgUrl: string
type: string
}

(2)在store/home.ts文件中封装接口,用于获取轮播图数据

import { ApiRes, BannerItem } from '@/types/data'
import request from '@/utils/request'
import { defineStore } from 'pinia'
export default defineStore('home', {
state: () => ({
bannerList: [] as BannerItem[],
}),
actions: {
async getBannerList() {
const {data: res} = await request.get<ApiRes<BannerItem[]>>('/home/banner')
this.bannerList = res.result
},
},
})

(3)在store/index.ts中导入

import useCategoryStore from './modules/category'
import useHomeStore from './modules/home'
export default function useStore() {
return {
category: useCategoryStore(),
home: useHomeStore(),
}
}

(4)通过开发者工具查看数据

<script lang="ts" setup>
import useStore from '@/store'
const { home } = useStore()
home.getBannerList()
</script>

轮播图-通用轮播图组件

项目中会多次使用到轮播图组件,但是轮播图渲染的数据是不一样的。

但是轮播图的基本功能都是一样的,比如图片切换,自动播放等等。

因此需要封装一个通用的轮播图组件。

(1)通用轮播图的基本结构src/components/carousel/index.vue

fade 类:用于控制图片的显示和隐藏

active 类:用于控制小圆点高亮

<script lang="ts" setup name="Carousel">
defineProps()
</script>
<template>
<div class="carousel">
<ul class="carousel-body">
<li class="carousel-item fade">
<RouterLink to="/">
<img
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg"
alt=""
/>
</RouterLink>
</li>
<li class="carousel-item">
<RouterLink to="/">
<img
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg"
alt=""
/>
</RouterLink>
</li>
<li class="carousel-item">
<RouterLink to="/">
<img
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg"
alt=""
/>
</RouterLink>
</li>
</ul>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev"
><i class="iconfont icon-angle-left"></i
></a>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next"
><i class="iconfont icon-angle-right"></i
></a>
<div class="carousel-indicator">
<span class="active"></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</template>
<style scoped lang="less">
.xtxcarousel {
width: 100%;
height: 100%;
min-width: 300px;
min-height: 150px;
position: relative;
.carousel {
&-body {
width: 100%;
height: 100%;
}
&-item {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0;
transition: opacity 0.5s linear;
&.fade {
opacity: 1;
z-index: 1;
}
img {
width: 100%;
height: 100%;
}
}
&-indicator {
position: absolute;
left: 0;
bottom: 20px;
z-index: 2;
width: 100%;
text-align: center;
span {
display: inline-block;
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
cursor: pointer;
~ span {
margin-left: 12px;
}
&.active {
background: #fff;
}
}
}
&-btn {
width: 44px;
height: 44px;
background: rgba(0, 0, 0, 0.2);
color: #fff;
border-radius: 50%;
position: absolute;
top: 228px;
z-index: 2;
text-align: center;
line-height: 44px;
opacity: 0;
transition: all 0.5s;
&.prev {
left: 20px;
}
&.next {
right: 20px;
}
}
}
&:hover {
.carousel-btn {
opacity: 1;
}
}
}
</style>

(2)全局注册通用轮播图 src/components/index.ts

import type { App } from 'vue'
import skelecton from './skeleton/index.vue'
+import Carousel from './carousel/index.vue'
export default {
install(app: App) {
app.component(skelecton.name, skelecton)
+ app.component(Carousel.name, Carousel)
},
}

(3)在广告组件中使用src/views/home/components/home-banner.vue

<template>
<div class="home-banner">
<!-- 轮播图 -->
<Carousel></XtxCarousel>
</div>
</template>

(4)覆盖样式,控制箭头和小圆点的位置src/views/home/components/home-banner.vue

:deep(.carousel-btn.prev) {
left: 270px!important;
}
:deep(.carousel-indicator) {
padding-left: 250px;
}

(5)查看效果

轮播图-数据渲染

目的

home-banner组件把数据传递给Carousel组件进行渲染

(1)父传子的方式将数据传给通用轮播图组件src/views/home/components/home-banner.vue

<Carousel :slides="home.bannerList"></Carousel>

(2)子组件接收数据src/components/carousel/index.vue

了解写法:如果通过js的方法定义类型,需要单独引入PropType进行编写

<script lang="ts" setup name="Carousel">
import { BannerItem } from '@/types/data'
// import { PropType } from 'vue'
// defineProps({
// slides: {
// type: Array as PropType<BannerItem[]>,
// required: true,
// },
// })
defineProps<{
slides: BannerItem[]
}>()
</script>

(3)渲染轮播图数据src/components/carousel/index.vue

<template>
<div class="carousel">
<ul class="carousel-body">
<li class="carousel-item fade" v-for="item in slides" :key="item.id">
<RouterLink :to="item.hrefUrl">
<img :src="item.imgUrl" alt="" />
</RouterLink>
</li>
</ul>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev">
<i class="iconfont icon-angle-left"></i>
</a>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next">
<i class="iconfont icon-angle-right"></i>
</a>
<div class="carousel-indicator">
<span v-for="item in slides" :key="item.id" class="active"></span>
</div>
</div>
</template>

(4)控制高亮的下标

<script lang="ts" setup name="Carousel">
const active = ref(0)
</script>

(5)高亮渲染

  • 添加的fade的图片才会展示,所以根据当前索引号进行判断,索引号等于active的才进行展示
  • 添加了active类名的小圆点才会高亮,高亮逻辑跟图片一致
<template>
<div class="carousel">
<ul class="carousel-body">
<li
class="carousel-item"
+ :class="{ fade: active === index }"
+ v-for="(item, index) in slides"
:key="item.id"
>
<RouterLink :to="item.hrefUrl">
<img :src="item.imgUrl" alt="" />
</RouterLink>
</li>
</ul>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev">
<i class="iconfont icon-angle-left"></i>
</a>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next">
<i class="iconfont icon-angle-right"></i>
</a>
<div class="carousel-indicator">
<span
+ v-for="(item, index) in slides"
:key="item.id"
+ :class="{ active: active === index }"
></span>
</div>
</div>
</template>

轮播图-逻辑封装

实现需求:

轮播图里面的图片需要从父组件传入(因为轮播组件可以复用)

父组件需要控制轮播图的是否自动播放、动画时间(处理默认值逻辑)

是否自动播放和动画时间都是需要默认值的(如果不传就可以使用轮播组件自己提供的默认值)

播放逻辑

  • 点击小圆点可以切换图片
  • 点击prev和next按钮可以播放指定图片(根据图片个数判断播放的循环)
  • 如果父组件配置了自动播放,则需要定时播放图片
  • 鼠标进入轮播图,暂停轮播
  • 鼠标离开轮播图,继续轮播
  • 注意点:组件卸载的时候需要清除定时轮播效果(不然组件重新加载的时候会导致多个定时器开启)

(1)父组件传值给轮播图src/views/home/components/home-banner.vue

<template>
<div class="home-banner">
<!-- 轮播图 -->
<Carousel :slides="slides" autoPlay :duration="3000"></XtxCarousel>
</div>
</template>

(2)props接收src/components/Carousel.vue

<script lang="ts" setup name="Carousel">
import { BannerItem } from '@/types/data'
import { ref, PropType } from 'vue'
defineProps({
slides: {
type: Array as PropType<BannerItem[]>,
required: true,
},
autoPlay: {
type: Boolean,
default: false,
},
duration: {
type: Number,
default: 3000,
},
})
const active = ref(0)
</script>

(3)轮播图的播放逻辑

<script lang="ts" setup name="Carousel">
import { BannerItem } from '@/types/data'
import { onMounted, onUnmounted, PropType, ref } from 'vue'
// import { PropType } from 'vue'
const props = defineProps({
slides: {
type: Array as PropType<BannerItem[]>,
required: true,
},
duration: {
type: Number,
default: 3000,
},
autoPlay: {
type: Boolean,
default: false,
},
})
// const props = defineProps<{
// slides: BannerItem[]
// }>()
// 控制高亮
const active = ref(0)
const prev = () => {
if (active.value <= 0) {
active.value = props.slides.length - 1
} else {
active.value--
}
}
const next = () => {
if (active.value >= props.slides.length - 1) {
active.value = 0
} else {
active.value++
}
}
const play = () => {
// 如果没有自动播放
if (!props.autoPlay) return
// 在ts中,使用定时器,window.setInterval
timer = window.setInterval(() => {
next()
}, props.duration)
}
const stop = () => {
clearInterval(timer)
}
let timer = -1
// 自动播放
onMounted(() => {
play()
})
onUnmounted(() => {
stop()
})
</script>

(4)鼠标进入和离开操作

<div class="carousel" @mouseenter="stop" @mouseleave="play">

(5)鼠标经过小圆点切换

<span
v-for="(item, index) in slides"
:key="item.id"
:class="{ active: active === index }"
@mouseenter="active = index"
></span>

(6)点击左右箭头切换

const prev = () => {
if (active.value === 0) {
active.value = props.slides.length - 1
} else {
active.value--
}
}
const next = () => {
if (active.value === props.slides.length - 1) {
active.value = 0
} else {
active.value++
}
}
// 注册事件
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev" @click="prev">
<i class="iconfont icon-angle-left"></i>
</a>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next" @click="next">
<i class="iconfont icon-angle-right"></i>
</a>
vascript
const prev = () => {
if (active.value === 0) {
active.value = props.slides.length - 1
} else {
active.value--
}
}
const next = () => {
if (active.value === props.slides.length - 1) {
active.value = 0
} else {
active.value++
}
}
// 注册事件
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev" @click="prev">
<i class="iconfont icon-angle-left"></i>
</a>
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next" @click="next">
<i class="iconfont icon-angle-right"></i>
</a>