vue無縫滾動的外掛開發填坑分享

chenxuan0000發表於2019-03-04

寫外掛的初衷

1.專案經常需要無縫滾動效果,當時寫jq的時候用用msClass這個老外掛,相對不上很好用。

2.後來轉向vue在vue-awesome沒有找到好的無縫滾動外掛,除了配置swiper可以實現但是相對來說太重了,於是自己造了個輪子。

3.在這分享下,當時寫這個外掛的坑,自己也複習下,如果程式碼上有瑕疵歡迎指出。

原始碼參考 vue-seamless-scroll

1.簡單的實現上下滾動基本版(最初版)

html

1.solt提供預設插槽位來放置父元件傳入的html

<template>
    <div @mouseenter="enter" @mouseleave="leave">
        <div ref="wrapper" :style="pos">
            <slot></slot>
       </div>
    </div>
</template>
複製程式碼

javascript

1.animationFrame 動畫api相容處理

2.arrayEqual 判斷陣列是否相等 來監聽data的變化來實現更新無縫滾動

<script>

  require('comutils/animationFrame')  //requestAnimationFrame api
  const arrayEqual = require('comutils/arrayEqual')
  export default {
    data () {
      return {
        yPos: 0,
        reqFrame: null
      }
    },
    props: {
      data: { // data 資料
        type: Array,
        default: []
      },
      classOption: { //引數
        type: Object,
        default: {}
      }
    },
    computed: {
      pos () {
        // 給父元素的style
        return {transform: `translate(0,${this.yPos}px)`}
      },
      defaultOption () {
        return {
          step: 1, //步長
          limitMoveNum: 5, //啟動無縫滾動最小資料數
          hoverStop: true, //是否啟用滑鼠hover控制
          direction: 1 //1 往上 0 往下
        }
      },
      options () {
        // 合併引數
        return Object.assign({}, this.defaultOption, this.classOption)
      }
      ,
      moveSwitch () {
      //判斷傳入的初始滾動值和data的length來控制是否滾動
        return this.data.length < this.options.limitMoveNum
      }
    },
    methods: {
      enter () {
        if (!this.options.hoverStop || this.moveSwitch) return
        cancelAnimationFrame(this.reqFrame)
      },
      leave () {
        if (!this.options.hoverStop || this.moveSwitch) return
        this._move()
      },
      _move () {
        //滾動
        this.reqFrame = requestAnimationFrame(
          () => {
            let h = this.$refs.wrapper.offsetHeight / 2
            let direction = this.options.direction
            if (direction === 1) {
              if (Math.abs(this.yPos) >= h) this.yPos = 0
            } else {
              if (this.yPos >= 0) this.yPos = h * -1
            }
            if (direction === 1) {
              this.yPos -= this.options.step
            } else {
              this.yPos += this.options.step
            }
            this._move()
          }
        )
      },
      _initMove () {
        if (this.moveSwitch) {
          cancelAnimationFrame(this.reqFrame)
          this.yPos = 0
        } else {
          this.$emit('copyData') //需要copy複製一份 emit到父元素  後期版本這裡已經優化
          if (this.options.direction !== 1) {
            setTimeout(() => {
              this.yPos = this.$refs.wrapper.offsetHeight / 2 * -1
            }, 20)
          }
          this._move()
        }
      }
    },
    mounted () {
      this._initMove()
    },
    watch: {
      //監聽data的變化
      data (newData, oldData) {
        if (!arrayEqual(newData, oldData.concat(oldData))) {
          cancelAnimationFrame(this.reqFrame)
          this._initMove()
        }
      }
    }
  }
</script>
複製程式碼

1.1 優化1: 新增配置openWatch 是否開啟data監控實時重新整理

有興趣可以看本次commit記錄 myClass.vue的更改

1.2 優化2: 新增配置singleHeight waitTime引數 控制是否單步滾動

commit記錄

1.3 優化3:新增對移動端touch事件滾動列表支援

commit記錄

1.4 優化4: 去掉了emit回撥(簡化初始化)

//原本元件呼叫
<my-class :data="listData" :class-option="classOption" @copy-data="listData = listData.concat(listData)">
//簡化後元件呼叫
<my-class :data="listData" :class-option="classOption" class="warp">
複製程式碼
用js的來複制一份innerHtml來代替之前的做法簡化使用
//this.$emit('copyData')

 timer = setTimeout(() => { //20ms延遲 作用保證能取到最新的html
   this.copyHtml = this.$refs.slotList.innerHTML
 }, 20)
 // template
 <template>
    <div @mouseenter="enter" @mouseleave="leave" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
        <div ref="wrap" :style="pos">
            <div ref="slotList" :style="float">
                <slot></slot>
            </div>
            <div v-html="copyHtml" :style="float"></div>
        </div>
    </div>
</template>
複製程式碼

commit記錄

1.5 bug1: 解決ie9下animationFrame報錯的bug

這個問題的原因查了比較久最後發現是當時沒有加return沒有取到定時器id

vue無縫滾動的外掛開發填坑分享

1.6 優化5:新增左右無縫滾動

類似上下可以檢視commit

1.7 Vue.use() 提供install全域性註冊

import vueMyCLass from './components/myClass.vue'

let myScroll

const defaultComponentName = 'vue-seamless-scroll'

// expose component to global scope
if (typeof window !== 'undefined' && window.Vue) {
  Vue.component('vue-seamless-scroll', vueMyCLass)
} else {
  myScroll = {
    install: function (Vue, options = {}) {
      Vue.component(options.componentName || defaultComponentName, vueMyCLass)
    }
  }

}

export default myScroll
複製程式碼

1.8 bug 解決了touchMove頻繁快速操作導致單步滾動失效bug 和部分程式碼優化

//1.封裝多次呼叫的取消動畫方法

_cancle: function _cancle() {
     cancelAnimationFrame(this.reqFrame || '');
    },
複製程式碼

//2.touchMove頻繁快速操作導致滾動錯亂bug

 _move () {
    this._cancle() //進入move立即先清除動畫 防止頻繁touchMove導致多動畫同時進行
    }    
複製程式碼

//3.生命週期結束前取消動畫

 beforeDestroy () {
      this._cancle()
}
複製程式碼

//4.修復不傳引數報警告的bug

 props: {
      data: {
        type: Array,
        default: () => {
          return []
        }
      },
      classOption: {
        type: Object,
        default: () => {
          return {}
        }
      }
    }
複製程式碼

//5.Fixing a bug. add a overflow:hidden on the child element

部分人喜歡用margin-top如果沒有overflow等限制會導致我裡面計算高度和實際有些許差距導致最後效果到臨界位置有輕微抖動 //預設加上了overflow: 'hidden'

computed: {
      float () {
        return this.options.direction > 1 ? {float: 'left', overflow: 'hidden'} : {overflow: 'hidden'}
      },
      pos () {
        return {
          transform: `translate(${this.xPos}px,${this.yPos}px)`,
          transition: `all ease-in ${this.delay}ms`,
          overflow: 'hidden'
        }
      }
}
複製程式碼

//6.新增單步滾動也能hover停止的功能

之前因為單步滾動內建了延遲執行this._move()預設單步限制了滑鼠懸停停止無縫滾動,後來通過給this._move()加上開關達到效果。

commit

TKS

如果對原生js實現類似的無縫滾動有興趣可以留言,我抽空也可以寫下seamless-scroll

vue-seamless-scroll發現bug或者有什麼不足望指點,感覺不錯點個star吧。

相關文章