vue无缝滚动的插件开发填坑分享

写插件的初衷

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

BVX}H{RC`{80TLA4}1A6XQL.png

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吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容