Vue3.0组件—banner轮播图(渐入渐隐效果)

组件产生

最近遇到一个需求,项目首页banner轮播,开始是直接使用element3.0的el-carousel走马灯效果,但是产品觉得切换太快,给用户的体验效果不好,经过多次修改产品给出切换的具体描述前一张图渐隐一个效果,后一张图渐显一个效果,然后停留几秒,于是单独写了一个banner轮播组件。

组件封装思路

  1. 主要内容:

    ①组件图片切换有三种方式,第一种是图片两边按钮切换,第二种是点击图片下方小横条切换,第三种是自动切换;

    ②子组件props的属性:listBanner(播放的图片源)、interval(图片播放的间隔时间)、 autoSwitc(是否自动播放banner)、 setHeight(banner的高度);

    ③给切换图片的标签添加ref,改变ref属性中的opacity,切换透明度,达到图片渐隐渐显的效果,再给对应标签样式中添加transition-duration: 1.5s;(图片透明度切换的时间

//上一张和下一张图片透明度切换
  const changePicture = (num) => {
   for (let i of rotationRef.value) {
    i.style.opacity = "0";//所有的图片透明度设置为0

   }
    //调用该方法的时候传入对应图片的索引,该图片赋透明度为1
   if (rotationRef.value.length !== 0) {    
       rotationRef.value[num].style.opacity = "1";
                                       }
  };
  1. 组件思路

    ①图片源是数组形式,首先用v-for遍历图片源数组,让图片先平铺到页面中

    ②实现点击左右两边按钮切换图片,点击右边按钮,图片switchIndex++(图片的索引)累加,点击左边按钮switchIndex–累减;从右边切换图片已经遍历完的时候,switchIndex赋值为0(第一张图片的索引),从头开始播放;从左边切换时,切换到图片switchIndex等于-1的时候,switchIndex赋值为图片源数组的长度-1(最后一张图片的索引)从最后一张图片播放。

    ③定时器切换图片,先写一个定时器,默认停留时间为4000ms,在定时器中调用右边切换的方法,因为定时器是顺时针播放图片的。onMounted中调用定时器方法,在 onUnmounted中要记得销毁定时器,不然会出现一些奇怪的问题。

    ④小横条切换图片,根据图片源数组的长度循环显示小横条,点击小横条的时候传入对应横条的索引,把这个索引赋值到对应切换图片的方法中,就会显示对应的索引的图片。

    ⑤鼠标停留mouseenter在图片上停止自动播放,鼠标离开mouseleave图片自动播放。停留的时候调用stopInterval();(清除定时器的方法),离开的时候调用addTimer();(开启定时器)

组件效果

在这里插入图片描述

完整代码

<template>
  <div
    class="banner-container"
    :style="{ height: height + 'px' }"
    @mouseenter="mouseenterEvent"
    @mouseleave="mouseleaveEvent"
  >
    <ul class="fade-banner">
      <li
        class="rotation-banner"
        ref="rotationRef"
        :style="{ height: height + 'px' }"
        v-for="(item, index) in listBanner"
        :key="index"
      >
        <img class="banner-img" @click="bannerLink" :src="item.img" :title="description" />
      </li>
    </ul>
    <span class="left-button" @click="switchBanner('left')"
      ><img src="@/assets/icons/common/left_icon.png" class="icon"
    /></span>
    <span class="right-button" @click="switchBanner('right')"
      ><img src="@/assets/icons/common/right_icon.png" class="icon"
    /></span>
    <ul class="banner-indicator">
      <span
        v-for="index in listBanner.length"
        :key="index"
        class="barExternal"
        @click="swatchIndicator(index - 1)"
      >
        <li
          ref="indicatorRef"
          class="default-indicator"
          :class="{ 'active-indicator': index - 1 === 0 }"
        ></li>
      </span>
    </ul>
  </div>
</template>

<script>
import { ref, onMounted, unref, onUnmounted } from "vue";

export default {
  name: "RollBanner",
  props: {
    //播放的banner
    listBanner: {
      type: Array,
      default: () => []
    },
    //banner自动播放的间隔时间
    interval: {
      type: Number,
      default: 3000
    },
    //是否自动播放banner
    autoSwitch: {
      type: Boolean,
      default: false
    },
    //banner高度
    setHeight: {
      type: Number,
      default: 400
    }
  },
  setup(props) {
    let tiemr = ref(null);
    const switchIndex = ref(0);
    const rotationRef = ref();
    const indicatorRef = ref();
    const description = ref(null); //图片描述
    let height = unref(props.setHeight);

    //banner播放方式
    const addTimer = () => {
      if (props.autoSwitch === true) {
        //自动播放
        tiemr = setInterval(() => {
          switchBanner("right");
        }, props.interval);
      }
    };

    // 清除定时器
    const stopInterval = () => {
      clearInterval(tiemr);
    };

    //banner文字提示(非必须)
    const switchDescription = () => {
      if (props.listBanner) {
        props.listBanner.map((item, index) => {
          if (index === switchIndex.value) {
            description.value = item.description;
          }
        });
      }
    };

    //鼠标进入停止自动播放(非必须)
    const mouseenterEvent = () => {
      stopInterval();
      switchDescription();
    };

    //鼠标离开自动播放(非必须)
    const mouseleaveEvent = () => {
      addTimer();
    };

    //上一张和下一张图片透明度切换
    const changePicture = (num) => {
      for (let i of rotationRef.value) {
        i.style.opacity = "0";
      }
      toBannerBar(num);
      if (rotationRef.value.length !== 0) {
        rotationRef.value[num].style.opacity = "1";
      }
    };

    //底部小横条颜色切换
    const toBannerBar = (val) => {
      for (let i of indicatorRef.value) {
        i.style.backgroundColor = "rgba(221, 221, 221, 0.541)";
      }
      if (indicatorRef.value.length !== 0) {
        indicatorRef.value[val].style.backgroundColor = "#ffffff";
      }
    };

    //左右切换banner
    const switchBanner = (value) => {
      if (value === "right") {
        switchIndex.value++;
        if (switchIndex.value === props.listBanner.length) {
          switchIndex.value = 0;
        }
      } else {
        switchIndex.value--;
        if (switchIndex.value === -1) {
          switchIndex.value = props.listBanner.length - 1;
        }
      }
      changePicture(switchIndex.value);
      switchDescription();
    };

    //底部横条切换banner
    const swatchIndicator = (value) => {
      switchIndex.value = value;
      changePicture(switchIndex.value);
      switchDescription();
    };

    //banner链接跳转((非必须)
    const bannerLink = () => {
      if (props.listBanner) {
        props.listBanner.map((item, index) => {
          if (index === switchIndex.value && item.url !== "" && item.url !== null) {
            window.location.href = item.url;
          }
        });
      }
    };

    onMounted(() => {
      addTimer();
    });
    onUnmounted(() => {
      stopInterval();
    });
    return {
      addTimer,
      bannerLink,
      switchIndex,
      indicatorRef,
      swatchIndicator,
      changePicture,
      rotationRef,
      toBannerBar,
      height,
      switchBanner,
      stopInterval,
      mouseenterEvent,
      mouseleaveEvent,
      description,
      switchDescription
    };
  }
};
</script>

<style lang="scss" scoped>
.banner-container {
  position: relative;
  .fade-banner {
    position: relative;
    list-style: none;
  }
  .rotation-banner {
    position: absolute;
    opacity: 0;
    // transition-duration: 3s;
    transition-duration: 1.5s;
    width: 100%;
    &:first-child {
      opacity: 1;
    }
  }
  .banner-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    cursor: pointer;
  }

  .left-button {
    position: absolute;
    cursor: pointer;
    top: 50%;
    left: 25px;
    width: 36px;
    height: 36px;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: rgb(31, 45, 61, 0.3);
    border-radius: 50%;
    opacity: 0;
  }
  .right-button {
    position: absolute;
    width: 36px;
    height: 36px;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    top: 50%;
    right: 25px;
    background-color: rgb(31, 45, 61, 0.3);
    border-radius: 50%;
    opacity: 0;
  }
  .icon {
    height: 20px;
    width: 20px;
  }
  .banner-indicator {
    position: absolute;
    left: 50%;
    bottom: 15px;
    transform: translateX(-50%);
    cursor: pointer;
    display: flex;
    .barExternal {
      height: 20px;
      display: flex;
      justify-content: center;
      align-items: center;
      .default-indicator {
        width: 30px;
        height: 2px;
        background: rgba(221, 221, 221, 0.541);
        display: block;
        float: left;
        margin-right: 10px;
        // &:first-child {
        //   background: #ffffff;
        // }
      }
      .active-indicator {
        background: #ffffff;
      }
    }
  }
  &:hover {
    .left-button {
      opacity: 1;
    }
    .right-button {
      opacity: 1;
    }
  }
}
</style>
GitHub 加速计划 / vu / vue
100
18
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:19 天前 )
9e887079 [skip ci] 11 个月前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 1 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐