轮播图组件封装, 我们主要分为三大步来完成:
1. 基本布局
2. 渲染结构
3. 逻辑封装
我们这里封装的轮播图组件和 Element UI 所封装的走马灯组件有所不同
Element UI 是直接渲染组件来决定的, 我们所封装的是根据数据来决定
首先我们来完成第一大步(基本布局)
思路分析:
1. 首先需要一个父盒子对轮播图组件进行约束, 父盒子根据情况来是否给定位; 设置宽高
2. 轮播图组件根据外部的父盒子的宽高走, 设置100%; 自身设置相对定位
3. 通过样式去设置好左右切换图片的按钮位置, 用户自定义图片位置的圆点位置和显示图片的位置
4. 显示的图片我们可以将所有的图片叠在一起, 定义一个 fade 类; 通过图片的下标值来动态的控制 opacity 类的值
5. 还需要设置 active 类, 用户点击圆点的时候; 将点击的那一个高亮
<template>
<div class='xtx-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>
</ul>
<!-- 左箭头 -->
<a href="javascript:;" class="carousel-btn prev"><i class="iconfont icon-angle-left"></i></a>
<!-- 右箭头 -->
<a href="javascript:;" class="carousel-btn next"><i class="iconfont icon-angle-right"></i></a>
<!-- 计数器 -->
<div class="carousel-indicator">
<span v-for="i in 5" :key="i"></span>
</div>
</div>
</template>
<script>
export default {
name: 'XtxCarousel'
}
</script>
<style scoped lang="less">
.xtx-carousel{
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类
&.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类
&.active {
background: #fff;
}
}
}
&-btn {
width: 44px;
height: 44px;
background: rgba(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>
第二步(渲染结构)
思路分析:
1. 我们结构是根据后端的数据来的, 所以轮播图组件需要暴露出一个 sliders 出去接收父组件传入来的数据
2. 轮播图组件内部还需要暴露出一个记录下标值的响应式变量, 它的默认值是 0 , 默认显示第一种图片和默认高亮第一个计步器按钮
<template>
<div class='xtx-carousel'>
<ul class="carousel-body">
<!-- 所有图片列表 -->
<li class="carousel-item" v-for="(item, i) in sliders" :key="item.id" :class="{fade: i===index}">
<RouterLink to="/">
<img :src="item.imgUrl" alt="">
</RouterLink>
</li>
</ul>
<!-- 左箭头 -->
<a href="javascript:;" class="carousel-btn prev"><i class="iconfont icon-angle-left"></i></a>
<!-- 右箭头 -->
<a href="javascript:;" class="carousel-btn next"><i class="iconfont icon-angle-right"></i></a>
<!-- 计数器 -->
<div class="carousel-indicator">
<span v-for="(item, i) in sliders" :key="item" :class="{active: i===index}"></span>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'XtxCarousel',
props: {
sliders: {
type: Array,
default: () => []
}
},
setup () {
// 记录图片的下标值变量
const index = ref(0)
return { index }
}
}
</script>
第三步(逻辑封装)
先梳理一下大致要实现的效果:
1. 需要兼容自动轮播效果
2. 鼠标进入停止自动轮播, 鼠标离开继续
3. 点击左右切换按钮, 切换前后图片
4. 点击计步器时, 切换对应的图片
5. 组件销毁时, 清除定时器
第一个需求(兼容自动轮播)
思路分析:
1. 首先是否自动轮播是用户来决定的, 且轮播时间间隔也是一样
2. 所以, 轮播图组件内部需要暴露出 autoPlay 和 duration 两个属性
3. 将自动轮播效果封装成一个函数
4. 使用定时器, 通过修改 index 值来控制图片的切换
5. 调用自动轮播是有条件的, sliders 是否有数据(没有图片的时候, 去做轮播是没有必要的); autoPlay 的值是否为真
<script>
import { ref, watch } from 'vue'
export default {
name: 'XtxCarousel',
props: {
// 轮播图数据
sliders: {
type: Array,
default: () => []
},
// 是否制动轮播
autoPlay: {
type: Boolean,
default: false
},
// 轮播时间间隔
duration: {
type: Number,
default: 3000
}
},
setup (props) {
// 记录图片的下标值变量
const index = ref(0)
let timer = null
const autoPlayFn = () => {
// 清除掉上一个定时器, 避免定时器的累加
// 特别是开发热更新的项目时
clearInterval(timer)
timer = setInterval(() => {
index.value++
if (index.value >= props.sliders.length) {
index.value = 0
}
}, props.duration)
}
// 监听sliders值的变化
watch(() => props.sliders, (newVal) => {
if (newVal.length && props.autoPlay) {
autoPlayFn()
}
// 加上immediate的含义在于, 可能父组件传进来的数据并不是发请求获取的数据
// 可能是定死的数据, 那么组件初始化的时候就会有数据
// 不加上immediate的话, 就永远监听不到sliders的变化的了
// 那么永远就不会做轮播
}, { immediate: true })
return { index }
}
}
</script>
// 因为autoPlay是一个布尔值, 所以直接写autoPlay就代表为true
<XtxCarousel :sliders="sliders" autoPlay />
第二个需求(鼠标进入停止轮播, 离开继续)
思路分析:
1. 定义一个 stop 函数, 判断如果 timer 存在; 则清除定时器
2. 定义一个 start 函数, 鼠标离开时开启轮播
3. @mouseenter事件调用 stop 函数, @mouseleave事件调用start函数
<template>
<div class='xtx-carousel' @mouseenter="stop" @mouseleave="start">
<ul class="carousel-body">
<!-- 所有图片列表 -->
<li class="carousel-item"
v-for="(item, i) in sliders" :key="item.id"
:class="{fade: i===index}"
>
<RouterLink to="/">
<img :src="item.imgUrl" alt="">
</RouterLink>
</li>
</ul>
......
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
name: 'XtxCarousel',
props: {
// 轮播图数据
sliders: {
type: Array,
default: () => []
},
// 是否制动轮播
autoPlay: {
type: Boolean,
default: false
},
// 轮播时间间隔
duration: {
type: Number,
default: 1000
}
},
setup (props) {
......
// 鼠标进入停止轮播
const stop = () => {
if (timer) clearInterval(timer)
}
// 鼠标离开开始轮播
const start = () => {
if (props.sliders.length && props.autoPlay) {
autoPlayFn()
}
}
return { index, stop, start }
}
}
</script>
第三个需求(点击左右切换按钮, 切换图片)
思路分析:
1. 封装一个 toggle 函数, 此函数接收一个 step 数据
2. 用户点击上一张按钮时 step 值为 -1, 下一张按钮时 step 值为 1
<template>
<div class='xtx-carousel' @mouseenter="stop" @mouseleave="start">
......
<!-- 左箭头 -->
<a @click="toggle(-1)" href="javascript:;" class="carousel-btn prev"><i class="iconfont icon-angle-left"></i></a>
<!-- 右箭头 -->
<a @click="toggle(1)" href="javascript:;" class="carousel-btn next"><i class="iconfont icon-angle-right"></i></a>
......
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
name: 'XtxCarousel',
......
setup (props) {
......
// 点击左右切换按钮, 切换图片
const toggle = (step) => {
// 重新定义一个变量的原因是, 可能index.value + step的值会大于可控范围
// 所以需要定义新的变量, 判断
const newIndex = index.value + step
if (newIndex > props.sliders.length - 1) {
index.value = 0
return
}
if (newIndex <= 0) {
index.value = props.sliders.length - 1
return
}
// 正常情况
index.value = newIndex
}
return { index, stop, start, toggle }
}
}
</script>
第四个需求(点击计步器切换按钮, 切换图片)
点击时, 将对于的下标值赋值
<!-- 计数器 -->
<div class="carousel-indicator">
<span
@click="index=i"
v-for="(item, i) in sliders" :key="item"
:class="{active: i===index}">
</span>
</div>
第五个需求(组件销毁时清除定时器)
<script>
// 组件销毁时, 清除定时器
onUnmounted(() => {
clearInterval(timer)
})
</script>