封装一个banner组件

1,050 阅读1分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

TIP 👉 疾风知劲草,岁寒见后凋。范晔《后汉书》

前言

在我们日常项目开发中,我们经常会写一些图标,所以封装了这款图标组件。

轮播图组件

属性

1. bannerList 数据数组
  • 数组中存放轮播图片的信息
  • 对象包含两个属性,src 和 link
    • src 的值为图片地址
    • link 的值为点击图片打开链接的地址
2. speed 动画时长
  • 单位:毫秒
  • 默认值:300
3. interval 轮播间隔时间
  • 单位:毫秒
  • 默认值:3000
4. defaultIndex 初始显示的轮播图的索引

默认值: 0

5. indexType 底部导航样式
  • 底部导航样式值为dot 或 line
    • dot 小圆点(默认值)
    • line 短横线

示例

<template>
  <div class="banners-demo">
    <banners :bannerList="imgList" :speed="1000" :interval="5000" :defaultIndex="2" indexType="line"></banners>
  </div>
</template>
<script>
import BaseBanners from '@/components/base/banners/index.vue'
export default {
  name: 'bannersDemo',
  data () {
    return {
      imgList: [
        {
          src: '/static/img/banner1.png',
          link: 'http://www.baidu.com'
        },
        {
          src: '/static/img/banner2.gif',
          link: 'http://www.hao123.com'
        },
        {
          src: '/static/img/banner3.jpg'
        }
      ]
    }
  },
  methods: {},
  components: {
    Banners
  }
}
</script>
<style lang="scss" scoped>
.banners-demo{
  width: 700px;
  height: 400px;
  margin: 30px auto 0;
}
</style>

实现banner.vue

<template>
  <div class="banners-container">
    <div class="banners-wrap" ref="bannersWrap">
      <div class="banner-item" v-for="item in banners" :key="item.src"
           :class="[item.animationClass]"
           :style="item.style"
           @click="linkHandle(item)">
      </div>
    </div>
    <div class="nav-wrap">
      <div class="nav-index-wrap" :class="[isStartNav && index === curIndex ? 'cur' : '']" v-for="(item, index) in bannerList" :key="item.src" @click="onChooseIndex(index)">
        <b class="nav-index" :class="indexClass">
          <span class="line-process" v-if="indexType === 'line'" :style="{transitionDuration: interval+'ms'}"></span>
        </b>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'banners',
  props: {
    bannerList: {
      type: Array,
      default: () => []
    },
    // 动画时长(毫秒)
    speed: {
      type: Number,
      default: 300
    },
    // 轮播间隔时间 (毫秒)
    interval: {
      type: Number,
      default: 3000
    },
    // 初始显示的轮播图的索引
    defaultIndex: {
      type: Number,
      default: 0
    },
    // 底部导航样式:dot(圆点)、line(横线),默认:dot
    indexType: {
      type: String,
      default: 'dot'
    }
  },
  data () {
    const curIndex = (this.defaultIndex < 0 || this.defaultIndex >= this.bannerList.length) ? 0 : this.defaultIndex
    return {
      curIndex, // 当前索引
      lastIndex: null, // 上一个banner的索引
      intervalId: null, // 定时器ID
      isStart: false, // 是否开始banner切换动画
      isStartNav: false, // 是否开始导航动画
      isReverse: false // 动画是否反向
    }
  },
  computed: {
    indexClass () {
      return 'nav-' + this.indexType
    },
    banners () {
      let list = this.bannerList
      let length = this.bannerList.length
      for (let i = 0; i < length; i++) {
        let animationClass = ''
        let style = {
          animationDuration: this.speed + 'ms',
          backgroundImage: `url('${list[i].src}')`
        }
        if (this.isStart) {
          if (this.isReverse) {
            if (i === this.curIndex) {
              animationClass = 'show-reverse'
            } else if (i === this.lastIndex) { // i === (this.curIndex + 1) % length)
              animationClass = 'hide-reverse'
            }
          } else {
            if (i === this.curIndex) {
              animationClass = 'show'
            } else if (i === this.lastIndex) { // (i === (this.curIndex === 0 ? length - 1 : this.curIndex - 1))
              animationClass = 'hide'
            }
          }
        } else {
          if (i === this.curIndex) {
            animationClass = 'cur'
          }
        }
        list[i].animationClass = animationClass
        list[i].style = style
      }
      return list
    }
  },
  mounted () {
    setTimeout(() => { this.isStartNav = true }, 10)
    this.intervalId = setInterval(this.start, this.interval)
    this.addListener()
  },
  methods: {
    linkHandle (item) {
      if (item.link) {
        window.open(item.link)
      }
    },
    start () {
      this.isStart = true
      this.isReverse = false
      this.lastIndex = this.curIndex
      let maxIndex = this.bannerList.length - 1
      if (this.curIndex >= maxIndex) {
        this.curIndex = 0
      } else {
        this.curIndex++
      }
    },
    onSwipe (direction) {
      if (direction) {
        clearInterval(this.intervalId)
        this.lastIndex = this.curIndex
        this.isReverse = direction < 0
        this.curIndex += direction
        let listLength = this.bannerList.length
        if (this.curIndex >= listLength) {
          this.curIndex = 0
        } if (this.curIndex < 0) {
          this.curIndex = listLength - 1
        }
        this.isStart = true
        this.intervalId = setInterval(this.start, this.interval)
      }
    },
    onChooseIndex (index) {
      if (this.curIndex !== index) {
        clearInterval(this.intervalId)
        this.lastIndex = this.curIndex
        this.curIndex = index
        this.isStart = true
        this.intervalId = setInterval(this.start, this.interval)
      }
    },
    addListener () {
      const bannersWrap = this.$refs.bannersWrap
      bannersWrap.addEventListener('touchstart', this.touchStartHandler)
      bannersWrap.addEventListener('touchmove', this.touchMoveHandler)
      bannersWrap.addEventListener('touchend', this.touchEndHandler)
    },
    touchStartHandler (e) {
      this.startX = e.touches[0].pageX
      this.moveEndX = this.startX
    },
    touchMoveHandler (e) {
      this.moveEndX = e.changedTouches[0].pageX
    },
    touchEndHandler (e) {
      if (this.moveEndX - this.startX > 50) {
        this.onSwipe(-1)
        e.stopPropagation()
      } else if (this.moveEndX - this.startX < -50) {
        this.onSwipe(1)
        e.stopPropagation()
      }
    }
  },
  destroyed () {
    clearInterval(this.intervalId)
  }
}
</script>
<style lang="scss" scoped px2rem="false">
$dotSize: 6px;
.banners-container{
  position: relative;
  height: 100%;
}
.banners-wrap{
  position: relative;
  overflow: hidden;
  height: 100%;
  white-space: nowrap;
  background-color: #cdcdcd;
  .banner-item{
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: #000 no-repeat center;
    background-size: cover;
    animation-timing-function: ease-in-out;
    z-index: 0;
  }
  .cur,.show,.show-reverse{
    z-index: 2;
  }
  .hide, .hide-reverse{
    z-index: 1;
  }
  .show{
    animation-name: show-in;
  }
  .hide{
    animation-name: show-out;
    transform: translate3d(-100%, 0, 0);
  }
  .show-reverse{
    animation-name: show-in-reverse;
  }
  .hide-reverse{
    animation-name: show-out-reverse;
    transform: translate3d(100%, 0, 0);
  }
}
.nav-wrap{
  position: absolute;
  bottom: 14px;
  width: 100%;
  text-align: center;
  white-space: nowrap;
  line-height: $dotSize * 2;
  z-index: 10;
  .nav-index-wrap {
    display: inline-block;
    .nav-index {
      display: inline-block;
      vertical-align: middle;
    }
    .nav-dot{
      margin: 5px 10px;
      width: $dotSize * 2;
      height: $dotSize *  2;
      border-radius: 50%;
      background-color: rgba(0,0,0,.5);
    }
    .nav-line{
      margin: 5px;
      width: 40px;
      height: 2px;
      background-color: rgba(255, 255, 255, .5);
      .line-process {
        display: block;
        height: 100%;
        width: 0;
        background-color: #fff;
        transition: none;
      }
    }
  }

  .cur{
    .nav-dot {
      background-color: rgba(255, 255, 255, .5);
    }
    .nav-line{
      background-color: rgba(255, 255, 255, .5);
      .line-process {
        width: 100%;
        transition-property: width;
        transition-timing-function: ease;
      }
    }
  }
}
@keyframes show-in{
  0% {
    transform: translate3d(100%, 0, 0);
  }
  100% {
    transform: translate3d(1px, 0, 0);
  }
}
@keyframes show-out{
  0% {
    transform: translate3d(1px, 0, 0);
  }
  100% {
    transform: translate3d(-100%, 0, 0);
  }
}
@keyframes show-in-reverse{
  0% {
    transform: translate3d(-100%, 0, 0);
  }
  100% {
    transform: translate3d(0, 0, 0);
  }
}
@keyframes show-out-reverse{
  0% {
    transform: translate3d(0, 0, 0);
  }
  100% {
    transform: translate3d(100%, 0, 0);
  }
}
</style>
// 下面注释勿删,用于根据配置文件 isMobile 配置项判断是否支持移动端,如果不支持,注释掉该样式
/* [auto html command START {isMobile=false}] */
<style lang="scss" scoped>
$dotSize: 8px;
@media (max-width: $max-mobile-width) {
  .nav-wrap{
    bottom: 10px;
    line-height: $dotSize * 2;/*no*/
    .nav-index-wrap {
      .nav-dot {
        width: $dotSize * 2; /*no*/
        height: $dotSize *  2; /*no*/
        border-radius: 50%; /*no*/
      }
      .nav-line {
        margin: 10px;
        width: 40px;
        height: 4px;
      }
    }
  }
  [data-dpr='1'] .nav-wrap{
    line-height: $dotSize;/*no*/
    .nav-dot{
      width: $dotSize;/*no*/
      height: $dotSize;/*no*/
    }
  }
  [data-dpr='3'] .nav-wrap{
    line-height: $dotSize * 3;/*no*/
    .nav-dot{
      width: $dotSize * 3;/*no*/
      height: $dotSize *  3;/*no*/
    }
  }
}
</style>
/* [auto html command END {isMobile=false}] */

感谢评论区大佬的点拨。

希望看完的朋友可以给个赞,鼓励一下