zoukankan      html  css  js  c++  java
  • 做一个vue轮播图组件

    根据huangyi老师的慕课网vue项目跟着做的,下面大概记录了下思路

    1.轮播图的图

    先不做轮播图逻辑部分,先把数据导进来,看看什么效果。在recommend组件新建一个recommends的数组,用这个数组来接受数据的录播图部分。然后再轮播图的插槽部分添加图片,代码如下

    <slider>
       <div v-for="(item,index) in recommends" :key="index">
           <a :href="item.linkUrl">
              <img :src="item.picUrl">
           </a>
        </div>
     </slider>
    
    // recommends.vue
    <script>
      data() {
        return {
          recommends: []
        }
    },
    methods: {
       _getRecommend() {
         getRecommend().then(res => {
           if (res.code === ERR_OK) {
             this.recommends = res.data.slider
             console.log(this.recommends)
           }
          )
       }
    },
    </script>
    

    但是现在轮播图是糊的,所以就要按着需求来自己做slider组件。

    首先,我们给轮播图sliderGroup,设置一个总的宽度。

    <div class="slider" ref="slider">
        <div class="slider-group" ref="sliderGroup">
          <slot></slot>
        </div>
        <div class="dots"></div>
      </div>
    

    要设置sliderGroup的宽度的话,我们要在渲染好dom元素的时候再设置宽度,所以我们要在mouted这个钩子函数里执行设置宽度,_setSliderWidth()和 _initSlider()分别是设置宽度和加入滚动效果。这里是为了分离,不让mounted这个钩子函数里有太多东西,然后不好改逻辑。

    mounted() {
        setTimeout(() => {
          this._setSliderWidth()
          this._initSlider()
        }, 20)
     },
     
    

    下面就是设置SliderGroup的宽度,其实中我们设置的主要方法,就是把slider的宽度给sliderGroupd的每个children,其中的slider-item属性是让他们左浮动的。然后让他们超出来的都隐藏掉。然后最后因为loop是循环轮播,要给slider前后各加一个宽度,这个是基础了,不懂得百度轮播图原理。然后最后让sliderGroup的宽度变成通过slot传进来的图片加2的宽度。

    methods: {
        _setSliderWidth() {
          this.children = this.$refs.sliderGroup.children
    
          let width = 0
          let sliderWidth = this.$refs.slider.clientWidth
          for (let i = 0; i < this.children.length; i++) {
            let child = this.children[i]
            addClass(child, 'slider-item')
    
            child.style.width = sliderWidth + 'px'
            width += sliderWidth
          }
          if (this.loop) {
            width += 2 * sliderWidth
          }
          this.$refs.sliderGroup.style.width = width + 'px'
        }
    }
    

    addClass方法不是系统自带的,是自己定义的,放在项目的src/common/js/dom/js里

    export function addClass(el, className) {
      if (hasClass(el, className)) {
        return
      }
    
      let newClass = el.className.split(' ')
      newClass.push(className)
      el.className = newClass.join(' ')
    }
    
    export function hasClass(el, className) {
      let reg = new RegExp('(^|\s)' + className + '(\s|$)')
      return reg.test(el.className)
    }
    
    

    在设置完宽度以后,需要在recommend.vue设置一下加入addClass的时间,因为getRecommend这个方法是异步的,所以如果在dom渲染完后的时候在执行addclass方法,此时还没有获得到数据,所以也就没有slot里面的数据,所以要在slder组件外侧的div中设置一个v-if

    <div v-if="recommends.length" class="slider-wrapper">
            <slider>
              <div v-for="(item,index) in recommends" :key="index">
                <a :href="item.linkUrl">
                  <img :src="item.picUrl">
                </a>
              </div>
            </slider>
          </div>
    

    当轮播图可以正确显示的时候,我们需要给轮播图添加滑动。我们用better-scroll,直接在npm上安装,然后在script标签里引入BScroll, 然后传入合适的参数,就可以了。

     _initSlider() {
          this.slider = new BScroll(this.$refs.slider, {
            scrollX: true,
            scrollY: false,
            momentum: false,
            snap: true,
            snapLoop: this.loop,
            snapThreshold: 0.3,
            snapSpeed: 400,
            click: true
          })
        }
    

    2.轮播图的dots

    首先,我们要通过children.length来新建dots,在哪里新建呢?在mounted里

    mounted() {
        setTimeout(() => {
          this._setSliderWidth()
          this._initDots()
          this._initSlider()
        }, 20)
    }
    

    然后顺应着新建一个_initDots方法,这样可以有效的分离,业务逻辑比较清晰。

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

    现在的程度是仅仅有dots的静态了(css做出样式),然后我们需要根据页面来设置active-dots。所以我们需要在_initSlider()方法中监听scrollEnd事件,这个时间是better-scroll的,如果没导入就没有。

    this.slider.on('scrollEnd', () => {
       let pageIndex = this.slider.getCurrentPage().pageX
       // 这个pageIndex -1是因为前面有一张为了无缝连接轮播图的。需要把他弄掉
        if (this.loop) {
          pageIndex -= 1
      	} 
    		this.currentPageIndex = pageIndex
    })
    

    然后配合js,我们在html绑定相应的class就行了。

     <div class="dots">
          <span
            class="dot"
            v-for="(item,index) in dots"
            :key="index"
            :class="{active:currentPageIndex === index}"
          ></span>
    </div>
    

    这样就就可以实现轮播带着dots一起动的效果了,接下来做自动播放功能

    3. 轮播图自动播放

    自动播放的时机,就是在新建轮播图完成的时候,也就是在mounted钩子里,定义一个_play方法

     mounted() {
        setTimeout(() => {
          this._setSliderWidth()
          this._initDots()
          this._initSlider()
          if (this.autoPlay) {
            this._play()
          }
        }, 20)
     }
    

    然后我们顺着去找methods里定义_play()这个方法。

    _play() {
          let pageIndex = this.currentPageIndex + 1
          if (this.loop) {
            pageIndex += 1
          }
          this.timer = setTimeout(() => {
            // 0 代表y方向,400表示间隔
            this.slider.goToPage(pageIndex, 0, 400)
          }, this.interval)
    }
    

    但是这个在mounted钩子里,我们只调用了依次goToPage方法。这很不爽。所以需要我们在想办法,让每次换页的时候都去调用一下,拿着还不好说嘛,用上次的scrollEnd事件,所以只需要在上次那个地方添加一些方法就OK了

    this.slider.on('scrollEnd', () => {
            let pageIndex = this.slider.getCurrentPage().pageX
            if (this.loop) {
              pageIndex -= 1
            }
            this.currentPageIndex = pageIndex
    
            if (this.autoPlay) {
              clearTimeout(this.timer)
              this._play()
            }
       })
    

    OK,现在轮播图的dots,滑动,自动播放功能就完成了。下面是组件完整的代码

    <template>
      <div class="slider" ref="slider">
        <div class="slider-group" ref="sliderGroup">
          <slot></slot>
        </div>
        <div class="dots">
          <span
            class="dot"
            v-for="(item,index) in dots"
            :key="index"
            :class="{active:currentPageIndex === index}"
          ></span>
        </div>
      </div>
    </template>
    
    <script type="text/ecmascript-6">
    import BScroll from 'better-scroll'
    import { addClass } from 'common/js/dom'
    export default {
      data() {
        return {
          dots: [],
          currentPageIndex: 0
        }
      },
      props: {
        // 是否可以循环轮播
        loop: {
          type: Boolean,
          default: true
        },
        // 是否可以自动轮播
        autoPlay: {
          type: Boolean,
          default: true
        },
        // 自动轮播时间间隔
        interval: {
          type: Number,
          default: 4000
        }
      },
      mounted() {
        setTimeout(() => {
          this._setSliderWidth()
          this._initDots()
          this._initSlider()
          if (this.autoPlay) {
            this._play()
          }
        }, 20)
        window.addEventListener('resize', () => {
          if (!this.silder) {
            return
          }
          this._setSliderWidth(true)
          this.slider.refresh()
        })
      },
      methods: {
        _setSliderWidth(isResize) {
          this.children = this.$refs.sliderGroup.children
    
          let width = 0
          let sliderWidth = this.$refs.slider.clientWidth
          for (let i = 0; i < this.children.length; i++) {
            let child = this.children[i]
            addClass(child, 'slider-item')
    
            child.style.width = sliderWidth + 'px'
            width += sliderWidth
          }
          if (this.loop && !isResize) {
            width += 2 * sliderWidth
          }
          this.$refs.sliderGroup.style.width = width + 'px'
        },
        _initSlider() {
          this.slider = new BScroll(this.$refs.slider, {
            scrollX: true,
            scrollY: false,
            momentum: false,
            snap: true,
            snapLoop: this.loop,
            snapThreshold: 0.3,
            snapSpeed: 400,
            click: true
          })
          this.slider.on('scrollEnd', () => {
            let pageIndex = this.slider.getCurrentPage().pageX
            if (this.loop) {
              pageIndex -= 1
            }
            this.currentPageIndex = pageIndex
    
            if (this.autoPlay) {
              clearTimeout(this.timer)
              this._play()
            }
          })
        },
        _initDots() {
          this.dots = new Array(this.children.length)
        },
        _play() {
          let pageIndex = this.currentPageIndex + 1
          if (this.loop) {
            pageIndex += 1
          }
          this.timer = setTimeout(() => {
            // 0 代表y方向,400表示间隔
            this.slider.goToPage(pageIndex, 0, 400)
          }, this.interval)
        }
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
    @import '~common/stylus/variable'
    .slider
      min-height: 1px
      .slider-group
        position: relative
        overflow: hidden
        white-space: nowrap
        .slider-item
          float: left
          box-sizing: border-box
          overflow: hidden
          text-align: center
          a
            display: block
             100%
            overflow: hidden
            text-decoration: none
          img
            display: block
             100%
      .dots
        position: absolute
        right: 0
        left: 0
        bottom: 12px
        text-align: center
        font-size: 0
        .dot
          display: inline-block
          margin: 0 4px
           8px
          height: 8px
          border-radius: 50%
          background: $color-text-l
          &.active
             20px
            border-radius: 5px
            background: $color-text-ll
    </style>
    
  • 相关阅读:
    一种简洁明了的权限管理系统
    css小技巧(1)
    多功能旋转木马轮播实例
    jquery双向列表选择器select版
    jquery双向列表选择器DIV模拟版
    单击页面任何地方关闭隐藏层
    用户登录体验之密码框设计
    扁平化设计的美感
    分析网站的用户行为
    app的架构和导航设计
  • 原文地址:https://www.cnblogs.com/wangzirui98/p/11041263.html
Copyright © 2011-2022 走看看