寫插件的初衷
1.項(xiàng)目經(jīng)常需要無(wú)縫滾動(dòng)效果缴罗,當(dāng)時(shí)寫jq的時(shí)候用用msClass這個(gè)老插件,相對(duì)不上很好用虱颗。
2.后來(lái)轉(zhuǎn)向vue在vue-awesome沒(méi)有找到好的無(wú)縫滾動(dòng)插件晶伦,除了配置swiper可以實(shí)現(xiàn)但是相對(duì)來(lái)說(shuō)太重了闸衫,于是自己造了個(gè)輪子献丑。
3.在這分享下末捣,當(dāng)時(shí)寫這個(gè)插件的坑,自己也復(fù)習(xí)下,如果代碼上有瑕疵歡迎指出创橄。
源碼參考 vue-seamless-scroll
1.簡(jiǎn)單的實(shí)現(xiàn)上下滾動(dòng)基本版(最初版)
html
1.solt提供默認(rèn)插槽位來(lái)放置父組件傳入的html
<template>
<div @mouseenter="enter" @mouseleave="leave">
<div ref="wrapper" :style="pos">
<slot></slot>
</div>
</div>
</template>
javascript
1.animationFrame 動(dòng)畫api兼容處理
2.arrayEqual 判斷數(shù)組是否相等 來(lái)監(jiān)聽(tīng)data的變化來(lái)實(shí)現(xiàn)更新無(wú)縫滾動(dòng)
<script>
require('comutils/animationFrame') //requestAnimationFrame api
const arrayEqual = require('comutils/arrayEqual')
export default {
data () {
return {
yPos: 0,
reqFrame: null
}
},
props: {
data: { // data 數(shù)據(jù)
type: Array,
default: []
},
classOption: { //參數(shù)
type: Object,
default: {}
}
},
computed: {
pos () {
// 給父元素的style
return {transform: `translate(0,${this.yPos}px)`}
},
defaultOption () {
return {
step: 1, //步長(zhǎng)
limitMoveNum: 5, //啟動(dòng)無(wú)縫滾動(dòng)最小數(shù)據(jù)數(shù)
hoverStop: true, //是否啟用鼠標(biāo)hover控制
direction: 1 //1 往上 0 往下
}
},
options () {
// 合并參數(shù)
return Object.assign({}, this.defaultOption, this.classOption)
}
,
moveSwitch () {
//判斷傳入的初始滾動(dòng)值和data的length來(lái)控制是否滾動(dòng)
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 () {
//滾動(dòng)
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復(fù)制一份 emit到父元素 后期版本這里已經(jīng)優(yōu)化
if (this.options.direction !== 1) {
setTimeout(() => {
this.yPos = this.$refs.wrapper.offsetHeight / 2 * -1
}, 20)
}
this._move()
}
}
},
mounted () {
this._initMove()
},
watch: {
//監(jiān)聽(tīng)data的變化
data (newData, oldData) {
if (!arrayEqual(newData, oldData.concat(oldData))) {
cancelAnimationFrame(this.reqFrame)
this._initMove()
}
}
}
}
</script>
1.1 優(yōu)化1: 新增配置openWatch 是否開(kāi)啟data監(jiān)控實(shí)時(shí)刷新
有興趣可以看本次commit記錄 myClass.vue的更改
1.2 優(yōu)化2: 新增配置singleHeight waitTime參數(shù) 控制是否單步滾動(dòng)
1.3 優(yōu)化3:添加對(duì)移動(dòng)端touch事件滾動(dòng)列表支持
1.4 優(yōu)化4: 去掉了emit回調(diào)(簡(jiǎn)化初始化)
//原本組件調(diào)用
<my-class :data="listData" :class-option="classOption" @copy-data="listData = listData.concat(listData)">
//簡(jiǎn)化后組件調(diào)用
<my-class :data="listData" :class-option="classOption" class="warp">
用js的來(lái)復(fù)制一份innerHtml來(lái)代替之前的做法簡(jiǎn)化使用
//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>
1.5 bug1: 解決ie9下animationFrame報(bào)錯(cuò)的bug
這個(gè)問(wèn)題的原因查了比較久最后發(fā)現(xiàn)是當(dāng)時(shí)沒(méi)有加return沒(méi)有取到定時(shí)器id
1.6 優(yōu)化5:添加左右無(wú)縫滾動(dòng)
類似上下可以查看commit
1.7 Vue.use() 提供install全局注冊(cè)
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頻繁快速操作導(dǎo)致單步滾動(dòng)失效bug 和部分代碼優(yōu)化
//1.封裝多次調(diào)用的取消動(dòng)畫方法
_cancle: function _cancle() {
cancelAnimationFrame(this.reqFrame || '');
},
//2.touchMove頻繁快速操作導(dǎo)致滾動(dòng)錯(cuò)亂bug
_move () {
this._cancle() //進(jìn)入move立即先清除動(dòng)畫 防止頻繁touchMove導(dǎo)致多動(dòng)畫同時(shí)進(jìn)行
}
//3.生命周期結(jié)束前取消動(dòng)畫
beforeDestroy () {
this._cancle()
}
//4.修復(fù)不傳參數(shù)報(bào)警告的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如果沒(méi)有overflow等限制會(huì)導(dǎo)致我里面計(jì)算高度和實(shí)際有些許差距導(dǎo)致最后效果到臨界位置有輕微抖動(dòng)
//默認(rèn)加上了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.新增單步滾動(dòng)也能hover停止的功能
之前因?yàn)閱尾綕L動(dòng)內(nèi)置了延遲執(zhí)行this._move()默認(rèn)單步限制了鼠標(biāo)懸停停止無(wú)縫滾動(dòng),后來(lái)通過(guò)給this._move()加上開(kāi)關(guān)達(dá)到效果。
TKS
如果對(duì)原生js實(shí)現(xiàn)類似的無(wú)縫滾動(dòng)有興趣可以留言莽红,我抽空也可以寫下seamless-scroll
vue-seamless-scroll發(fā)現(xiàn)bug或者有什么不足望指點(diǎn),感覺(jué)不錯(cuò)點(diǎn)個(gè)star吧妥畏。