在recommend.vue文件中,结合Better-scroll组件,开发轮播图组件,总结如下:
1.创建slider.vue并设置属性:是否循环、是否自动播放、播放间隔(通过props)
2.组件初始化(mounted钩子中)
3.外部容器根据轮播图片计算宽度
4.图片自动播放
5.底部dots高亮
6.处理一些细节:keep-alive、resize等

1.创建slider.vue组件

<template>
    <div class="slider" ref="slider">
        <div class="slider-group" ref="sliderGroup">
            //外部调用组件的 
            <slot></slot>
        </div>
        <div class="dots">
             <span class="dot" :class="{active:currentPageIndex === index}" v-for="(item,index) in dots" :key=index></span>
        </div>
    </div>
</template>

props: {
    loop: {//可循环
      type: Boolean, 
      default: true
    },
    autoPlay: { //自动播放
      type: Boolean,
      default: true
    },
    interval: {//间隔
      type: Number,
      default: 4000
    }
}

2.组件初始化

 mounted() {
    //要等到dom加载完毕可以使用this.$nextTick,这里推荐使用 setTimeout(() => {}, 20)  
    //因为浏览器刷新dom是17ms,所以这里使用了20ms
    setTimeout(() => {
      this._setSliderWidth()  //2.1初始化外部容器的宽度
      
      this._initDots()        //2.2初始化底部dots: initDost只能在此执行,这个时候this.children.length:5   
      //如果在initSlider后执行,因为已经new BScroll,并且this.loop,所以首尾添加两个元素this.children: 7
      
      this._initSlider()      //2.3初始化Better-scrolll轮播图
      
      //根据属性设置,如果自动播放则自动播放
      if (this.autoPlay) {
        this._play()
      }
    }, 20)
    ·····
  },

2.1初始化外部容器的宽度

 //isResize:是否resize出发的重新计算
 _setSliderWidth(isResize) {
       
      this.children = this.$refs.sliderGroup.children//获取sliderGroup轮播图中几张图片
      let width = 0
      let sliderWidth = this.$refs.slider.clientWidth//取到当前的轮播图的宽度(即为父元素的宽度)

      for (let i = 0; i < this.children.length; i++) {
        let child = this.children[i]
        //给每个轮播图加class,  轮播图的结构下a  img 设置样式
        addClass(child, 'slider-item')
        child.style.width = sliderWidth + 'px' //设置每个轮播图的宽度
        width += sliderWidth
      }
      //this.loop:如果是循环播放并且是首次初始化时,需要再加上头尾两个重复图片的宽度。
      //isResize:当浏览器调整大小的时候,父元素中的子元素已经包含了首位重复图片的元素,
      //因此不需要进行宽度的增加。
      if (this.loop && !isResize) {
        width += 2 * sliderWidth
      }
      
     //BScrol的横向是无法撑开的,因此需要根据内部几张图片的宽度来设置外部容器的宽度,
     //即this.$refs.sliderGroup.style.width=轮播图图片宽度的和
      this.$refs.sliderGroup.style.width = width + 'px'
    },

2.2初始化底部dots

初始化dots的个数

_initDots() {
  this.dots = new Array(this.children.length)
},

2.3初始化Better-scroll轮播图:最新版本的Better-scroll

_initSlider() {
  //创建BScroll实例,并设置配置项,
  this.slider = new BScroll(this.$refs.slider, {
      scrollX: true,     //横向移动
      scrollY: false,    //不能纵向移动
      momentum: false,
      snap: {
        loop: this.loop,
        threshold: 0.3,
        speed: 400
      }
  })
  //监听每一个图片滚动结束事件
  this.slider.on('scrollEnd', this._onScrollEnd)
  //touchend事件,手动拖动
  this.slider.on('touchend', () => {
    if (this.autoPlay) {
        this._play()
      }
  })
  //触发时机:滚动开始之前
  this.slider.on('beforeScrollStart', () => {
      if (this.autoPlay) {
        clearTimeout(this.timer)
      }
  })
},
 _onScrollEnd() {
    //获取当前页面的信息,pageX 横轴方向页面数
    let pageIndex = this.slider.getCurrentPage().pageX
    
    //currentPageIndex给dots高亮使用
    this.currentPageIndex = pageIndex
    
    //如果循环播放,则播放下一页
    if (this.autoPlay) {
      this._play()
    }
  },
  //轮播图播放
  _play() {
    //播放之前清除上一个,或者因为手动拖动导致定时器没有完全出发的‘剩余定时器’
    clearTimeout(this.timer)
    this.timer = setTimeout(() => {
      //滚动到下一个页面
      this.slider.next()
    }, this.interval)
  }

3.keep-alive

组件加上了keep-alive,所以在相应的生命周期中做相应处理

activated() {
  this.slider.enable()  //启用 better-scroll, 默认 开启
  let pageIndex = this.slider.getCurrentPage().pageX //获取当前页面的信息,pageX 横轴方向页面数

 //当我们做 slide 组件的时候,slide 通常会分成多个页面。调用此方法可以滚动到指定的页面
 //参数:x 横轴的页数  y 纵轴的页数   time 动画执行的时间
  this.slider.goToPage(pageIndex, 0, 0) 

  this.currentPageIndex = pageIndex
  if (this.autoPlay) {
    this._play()
  }
},
deactivated() {
  this.slider.disable()  //禁用 better-scroll,DOM 事件(如 touchstart、touchmove、touchend)的回调函数不再响应
  clearTimeout(this.timer)  //清除定时器
},

4.组件beforeDestroy

页面切走,组件会调用destroyed来销毁实例,这时清除定时器,利于内存的释放

beforeDestroy() {
  this.slider.disable()
  clearTimeout(this.timer)
},

5.window.resize事件

在mouted生命周期钩子中定义此事件

 window.addEventListener('resize', () => {
     //enabled判断当前 scroll 是否处于启用状态
    if (!this.slider || !this.slider.enabled) {
      return
    }
    clearTimeout(this.resizeTimer)
    
    this.resizeTimer = setTimeout(() => {
      //判断当前 scroll 是否处于滚动动画过程中
      //当开启 CSS3 Transition 动画时判断该值
      if (this.slider.isInTransition) {
        this._onScrollEnd()
      } else {
        if (this.autoPlay) {
          this._play()
        }
      }
      //重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常
      this.refresh()
    }, 60)
  })

refresh() {
    if (this.slider) {
      this._setSliderWidth(true)
      //重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常
      this.slider.refresh()
    }
},

6.使用

在recomment.vue中使用slider.vue轮播图

 <div v-if="recommends.length" class="slider-wrapper">
  <div class="slider-content">
    <slider ref="slider">
      <div v-for="item in recommends">
        <a :href="item.linkUrl">
          <img @load="loadImage" :src="item.picUrl">
        </a>
      </div>
    </slider>
  </div>
</div>

6.1 v-if="recommends.length"

在recommend.vue中,this.recommends数据的获取是异步过程,因此加上v-if指令,在数据获取后才渲染slider.vue组件

6.2 图片的加载过程

图片的加载onload是一个异步过程,因此图片加载完成后slider需要refresh()
而且只需要有一个图片加载完成即可,所以加个标志位checkloaded

loadImage() {
    if (!this.checkloaded) {
      this.checkloaded = true
      setTimeout(() => {
        this.$refs.scroll.refresh()
      }, 20)
    }
  },

7.css部分

7.1 slider.vue

图片描述

7.2 recommend.vue

图片描述


云深不知处
431 声望10 粉丝