【Vue】手写一个轮播图组件

826 阅读2分钟

先上效果图

f49f0d25-6577-46ae-99c3-c544128334f6.gif

完整代码

<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">&larr;</div>
      <div class="toggle-item" @click="next">&rarr;</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 来实现,可以将 setIntervaltimeout 参数作为组件的属性,由父组件传值进来进而控制轮播图自动播放的速度快慢。

// 开始轮播
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--
}

因为控制器的显示与隐藏是和鼠标是否移入轮播图中有关的,鼠标移入轮播图时,显示控制器,鼠标移出轮播图时,隐藏控制器,如果又使用 mouseentermouseleave 事件来控制就有点麻烦,这里可以使用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">&larr;</div>
  <div class="toggle-item" @click="next">&rarr;</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。

缺陷

  1. 切换不够丝滑,element的轮播图组件用的是 translate 来实现的,我这个是用 v-show 也就是控制元素的 display 来实现的。
  2. 组件的属性太少,可定制性程度不高,指示器样式,控制器样式,轮播方向都应该可定制。