先上效果图
完整代码
<script setup lang="ts">
import {ref, defineProps} from "vue";
// 定义组件属性
const props = defineProps({
list: { // 要轮播的图片的数组
type: Array as () => Array<Object>,
required: true,
default: () => {
return []
}
}
})
// 当前显示的图片在list数组中的下标
let active = ref<number>(1)
// 定时器
let timer: any = null;
// 开始轮播
const startCarousel = (): void => {
if (timer) return
timer = setInterval(() => {
if (active.value >= props.list.length) {
active.value = 1
return
}
active.value++
}, 1000)
}
// 停止轮播
const stopCarousel = (): void => {
clearInterval(timer)
timer = null
}
// 上一张
const pre = (): void => {
if (active.value === 1) {
active.value = props.list.length
return
}
active.value--
}
// 下一张
const next = (): void => {
if(active.value === props.list.length){
active.value = 1
return
}
active.value++
}
startCarousel()
</script>
<template>
<div class="co-carousel"
@mouseenter="stopCarousel"
@mouseleave="startCarousel">
<div class="co-carousel-container">
<template v-for="(item,index) in list">
<div class="co-carousel-item"
v-show="active === index+1">
<img
:src="item.url"
:alt="item.name"/>
</div>
</template>
</div>
<div class="co-carousel-toggle">
<div class="toggle-item" @click="pre">←</div>
<div class="toggle-item" @click="next">→</div>
</div>
<!-- 轮播图操作区域:指示器、箭头等-->
<div class="co-carousel-operation">
<!-- 指示器-->
<div class="co-carousel-indicator">
<template v-for="n in list.length">
<div
class="indicator-item"
:class="n === active ? 'is-active' :''"
@mouseenter="active=n"
/>
</template>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "CoCarousel",
}
</script>
<style scoped>
.co-carousel {
width: 600px;
height: 400px;
margin: 0 auto;
position: relative;
cursor: pointer;
}
.co-carousel-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
flex-wrap: wrap;
flex-direction: row;
overflow: hidden;
}
.co-carousel-item {
width: 100%;
height: 100%;
position: absolute;
z-index: 0;
/*transition: width .5s ease-in-out;*/
}
.co-carousel-item__is-active{
/*width: 100%;*/
}
.co-carousel-item > img {
width: 100%;
height: 100%;
}
.co-carousel-toggle {
display: flex;
justify-content: space-between;
padding: 0 10px;
position: absolute;
width: calc(100% - 20px);
top: 45%;
}
.toggle-item {
width: 40px;
height: 40px;
background: white;
color: #878789;
line-height: 40px;
text-align: center;
border-radius: 50%;
opacity: 0;
font-size: 18px;
transition: opacity .3s ease-in-out;
}
.co-carousel-toggle:hover > .toggle-item,
.co-carousel-container:hover + .co-carousel-toggle > .toggle-item {
opacity: 1;
}
.co-carousel-operation {
display: flex;
justify-content: center;
position: absolute;
bottom: 10px;
width: 100%;
}
.co-carousel-indicator {
display: flex;
position: relative;
z-index: 10;
}
.indicator-item {
width: 20px;
height: 6px;
opacity: 0.6;
background: #FFFFFF;
border-radius: 2px;
margin: 2px;
transition: width .3s ease-in-out;
}
.indicator-item:hover,
.indicator-item.is-active {
opacity: 1;
width: 40px;
}
</style>
思路分析
轮播图的自动播放功能通过定时函数 setInterval
来实现,可以将 setInterval
的 timeout
参数作为组件的属性,由父组件传值进来进而控制轮播图自动播放的速度快慢。
// 开始轮播
const startCarousel = (): void => {
if (timer) return
timer = setInterval(() => {
if (active.value >= props.list.length) {
active.value = 1
return
}
active.value++
}, 1000) // 这里的1000,可以变为通过属性来控制
}
startCarousel
函数的第一行代码 if(timer) return
的作用是保证同一时间只有一个定时器在运行。
当鼠标移入轮播图时,轮播图应该停止轮播,保持在当前图片,鼠标移出后又继续从当前图片开始轮播。这里使用的是鼠标移入事件 mouseenter
和鼠标移出事件 mouseleave
来实现,为轮播图的最外层容器 div.co-carousel
绑定这两个事件。
<div class="co-carousel"
@mouseenter="stopCarousel"
@mouseleave="startCarousel">
...
</div>
当鼠标移入轮播图时,执行 stopCarousel
函数,将会清空定时器,停止轮播。
当鼠标移出轮播图时,执行 startCarousel
函数,重新设定定时器,继续开始轮播。
当鼠标移入指示器时,轮播图应该定位到对应的位置,这里也是用 mouseenter
事件来实现,当鼠标移入时,将 active
的值改变为指定的值。
<!-- 指示器-->
<div class="co-carousel-indicator">
<template v-for="n in list.length">
<div
class="indicator-item"
:class="n === active ? 'is-active' :''"
@mouseenter="active=n"
/>
</template>
</div>
除了指示器,还可以有上一张和下一张的控制器,其实核心逻辑都是改变 active
的值,不过改变的时候要考虑边界条件,比如说当处于第一张轮播图的时候点击上一张的按钮,是应该保持在第一张还是说跑到最后一张呢?我这里选择的是跑到最后一张。
// 上一张
const pre = (): void => {
if (active.value === 1) {
active.value = props.list.length
return
}
active.value--
}
因为控制器的显示与隐藏是和鼠标是否移入轮播图中有关的,鼠标移入轮播图时,显示控制器,鼠标移出轮播图时,隐藏控制器,如果又使用 mouseenter
和 mouseleave
事件来控制就有点麻烦,这里可以使用CSS来控制。
<div class="co-carousel-container">
<template v-for="(item,index) in list">
<div class="co-carousel-item"
v-show="active === index+1">
<img
:src="item.url"
:alt="item.name"/>
</div>
</template>
</div>
<div class="co-carousel-toggle">
<div class="toggle-item" @click="pre">←</div>
<div class="toggle-item" @click="next">→</div>
</div>
.toggle-item {
width: 40px;
height: 40px;
background: white;
color: #878789;
line-height: 40px;
text-align: center;
border-radius: 50%;
opacity: 0;
font-size: 18px;
transition: opacity .3s ease-in-out;
}
.co-carousel-toggle:hover > .toggle-item,
.co-carousel-container:hover + .co-carousel-toggle > .toggle-item {
opacity: 1;
}
上面的并集选择器的意思就是
当鼠标移入 div.co-carousel-toggle
时,其下拥有 toggle-item
class的元素的透明度 opacity
变为1(100%),
当鼠标移入 div.co-carousel-caontainer
时,和 div.co-carousel-container
相邻的 div.co-carousel-toggle
下的 div.toggle-item
的透明度变为1。
缺陷
- 切换不够丝滑,element的轮播图组件用的是
translate
来实现的,我这个是用v-show
也就是控制元素的display
来实现的。 - 组件的属性太少,可定制性程度不高,指示器样式,控制器样式,轮播方向都应该可定制。