vue-seamless-scroll从入坑到放弃
- 一、vue-seamless-scroll是什么?
- 二、使用步骤
- 1.引入库
- 2.具体使用
- 三、下面就是我的踩坑之路
- 1.vue-seamless-scroll中的事件丢失问题
- 2. 解决事件丢失问题
- 3.vue-seamless-scroll 中异步数据的问题
- 4.数据时有时无解决
- 四、 vue-seamless-scroll 源码理解
- 1.页面结构分析
- 2. js逻辑部分分析
- 2.1 属性简单介绍
- 2.2 computed属性简单介绍
- 2.3 methods方法介绍
- _initMove初始化方法源码分析(主要)
- _move滚动方法源码分析(主要)
- 五、 个人插件推荐
- 结语
- 参考质料
一、vue-seamless-scroll是什么?
vue-seamless-scroll是vue当前使用最多的一个列表循环滚动插件。它功能强大使用方便,这也是我为什么选择它来入坑的原因。
二、使用步骤
1.引入库
//安装vue-seamless-scroll插件
npm install vue-seamless-scroll --save
//在mian.js文件中引入
import scroll from 'vue-seamless-scroll'
Vue.use(scroll)
2.具体使用
先贴上官方示例地址
//此处为官方示例地址的demo
<template>
<vue-seamless-scroll :data="listData" class="seamless-warp">
<ul class="item">
<li v-for="item in listData">
<span class="title" v-text="item.title"></span><span class="date" v-text="item.date"></span>
</li>
</ul>
</vue-seamless-scroll>
</template>
<style lang="scss" scoped>
.seamless-warp {
height: 229px;
overflow: hidden;
}
</style>
<script>
export default {
data () {
return {
listData: [{
'title': '无缝滚动第一行无缝滚动第一行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第二行无缝滚动第二行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第三行无缝滚动第三行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第四行无缝滚动第四行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第五行无缝滚动第五行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第六行无缝滚动第六行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第七行无缝滚动第七行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第八行无缝滚动第八行',
'date': '2017-12-16'
}, {
'title': '无缝滚动第九行无缝滚动第九行',
'date': '2017-12-16'
}]
}
}
}
</script>
可以看出使用的方式没什么难度,这里也贴出官网给出要注意的地方。
注意:需要给父容器一个height和:data='Array’和overfolw:hidden;左右滚动需要给ul容器一个初始化 css width。
如果以上步骤没有你都没有出错就应该可以看到效果了。
三、下面就是我的踩坑之路
1.vue-seamless-scroll中的事件丢失问题
我们的业务逻辑大概是这样的
刚开始我还以为可以开心的使用vue-seamless-scroll来做这个效果,但在实现这个滚动容器之后就发现了问题。这里面的点击事件时有时无。
只能说我心态崩了,这是什么情况?打开F12参看他的滚动容器,发现了他的一个惊天的秘密。
原来他的实现方式是自己复制了一份相同的dom,贴在了原来dom的后面来实现循环滚动的。看到这里我就猜到问题出现在他复制出来的dom中。又重新试了一下所有的点击事件,果然有一个dom容器中的点击事件完全没有问题,而另一个中的点击事件没有任何相应。
碰到这个问题的同学我想肯定不会很少吧。估计都会自己去重新实现一个组件了吧。别急往下看。
2. 解决事件丢失问题
使用 @click="scrollClick($event)"的方式在外层父元素上添加点击事件来获取点击的子dom的方式,再获取其中的数据来进行下一步的事件处理。
<div
class="v-scroll"
v-if="isScroll(item.title)"
@click="scrollClick($event)">
<vue-seamless-scroll :data="newData" class="list-style ">
...
</vue-seamless-scroll>
</div>
父容器的事件处理
scrollClick(e) {
//通过 e.target获取点击的dom
//通过e.target.dataset获取vuedom中的自定义属性
let data = e.target.dataset;
this.$emit("supervise-detail", data.name, data.type);
}
在要监听点击事件的子dom中的处理
<span
:title="itemObj.number2"
:data-name="itemObj.name"
:data-type="'all'"
>/{{ itemObj.number2 }}</span
>
</span>
这里使用data-属性的方式来进行属性绑定,以方便父容器的点击事件中获取该属性。 这样就可以通过其中的属性来处理相应的事件内容了。
3.vue-seamless-scroll 中异步数据的问题
这个项目的业务逻辑是这样的,
这个问题也是比较头痛的,当时看到的效果是滚动容器中显示的数据时有时无。
原因是其中的数据可能是多个接口返回回来的。所以vue-seamless-scroll的data更新达不到统一。所以在有些数据还没有返回时vue-seamless-scroll就将dom复制出来,而复制出来的dom中的数据不是响应式的,所有就会出现数据时有时无的情况。
这也使我想到了其中的解决办法。接着往下看。
4.数据时有时无解决
这里可以使用promise、generator或async的方式来解决异步的问题,使接口顺序调用。 具体实现就不贴出来了。网上有很多。然后在最后一个接口数据成功返回时,添加一个是否已全部加载的字段。并为其赋值为true。以用来在vue-seamless-scroll上使用v-if来控制vdom的渲染。
2、使用key来使vue-seamless-scroll重新渲染 原理都是一样的,就是通过vue-seamless-scroll的渲染来使其重新复制已经赋好值的dom。具体使用如下:
<vue-seamless-scroll ref="scrollModule" :data="newData" class="list-style " :key="scrollKey">
......
</vue-seamless-scroll>
在vue的data中添加一个scrollKey,然后绑定在vue-seamless-scroll 的组件上。
methods: {
......
upDateKey() {
this.scrollKey++;
},
},
$refs.设置的名称.upDateKey()来修改key值,使其重新渲染。
关于key的使用不清楚的同学可以看一下下面的文章。
Vue 中 Key的详细解释.
这时只需这接口数据成功返回时调用其方法就可以了。 你以为这就完了?再来扒一扒它的源码吧
四、 vue-seamless-scroll 源码理解
在已经引入的项目中,打开node_module库就可以看到其源码了。结构也很简单,主要的就index.js与myClass.vue文件。 index.js文件是写vue插件时候的一些配置。感兴趣的可以搜一搜vue插件开发。主要的就是myClass.vue文件。
1.页面结构分析
查看如下:
<template>
<div ref="wrap">
<div :style="leftSwitch" v-if="navigation" :class="leftSwitchClass" @click="leftSwitchClick">
<slot name="left-switch"></slot>
</div>
<div :style="rightSwitch" v-if="navigation" :class="rightSwitchClass" @click="rightSwitchClick">
<slot name="right-switch"></slot>
</div>
<div
ref="realBox"
:style="pos"
@mouseenter="enter"
@mouseleave="leave"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
>
<div ref="slotList" :style="float">
<slot></slot>
</div>
<div v-html="copyHtml" :style="float"></div>
</div>
</div>
</template>
这是它全部的页面结构代码,是不是很简洁。在这部分我们主要关注的就是ref="realBox"的div标签。这个就是它实现循环滚动的主要dom容器。可以看到这个dom容器上绑定了很多事件,这个就是用来监听pc端的鼠标移入移出与移动端的手指触摸事件。在继续看他里面的结构。很明显可以看到有一个包含slot标签的dom容器,和一个带有v-html="copyHtml"的dom容器。
使用slot插入进来的dom就是数据拥有响应式监听,事件保存完好的源dom(我们自己插入的dom)。而使用v-html 复制出来的dom,就是数据没有响应式,事件丢失的dom。OK继续往下看。
2. js逻辑部分分析
2.1 属性简单介绍
上面这一部分都是一些基础参数,根据图上注释理解即可。继续走着。
2.2 computed属性简单介绍
接下来就是它的computed属性。
看第一个注释从leftSwitchState到rightswitch都是在使用它的switch功能时用到的,这偏文章就不做解释了。而且基本上都是一两行代码,注释也写的很清楚。所以感兴趣的同学可以自行去研究。
接下来两个就是比较重要的属性:
float属性就是控制两个滚动容器是水平排列还是垂直排列的动态样式。 pos属性,他就是控制滚动容器的滚动方向与速度的动态样式。
ok继续往下看
这些属性就比较重要了下面来一个一个讲
defaultOption属性就是滚动容器的默认配置,如果一些属性没用自行配置就会取其中默认的配置。
options属性就是将传入的配置属性与默认属性进行合并,得到的最终配置参数的。
navigation属性是控制switch模式下的切换按钮是否显示的。 autoPlay属性是控制容器是否自动滚动的,这里代码也很简单。如果为switch模式(是否显示switch的切换按钮)默认不自动滚动,而不是swich模式就取传入进来的值来判断是否自动滚动。
继续走着
这个就是剩下的computed属性,上面的截图中也做了一些简单的解释。取几个主要的我在说一下。
baseFontSize属性是计算字体的基础大小的,主要是用来为realSingleStopWidth与realSingleStopHeight属性方便计算对应的宽度与高度。
step属性是计算滚动的步长(距离)的。在获取自身的步长时,也将单步滚动的步长计算了看出来。这里作者也对应做出了相对应的警告,警告的内容就是上面的console.error中的内容。
看到这里一些基本的属性,就全部看完了。是不是没什么太复杂的地方。好我们继续往下看。我们下一步该考虑使用这个组件时他是从哪一步开始执行的,我想聪明的你很快就已经想到了。没错就是mounted钩子函数。
2.3 methods方法介绍
那我们继续走着
这不要太简单,我本来想只截下mounted中的代码,可是只有一行。所以把methods中的方法也贴出来了。那我们就先大致了解一下(我们先猜测一下)。
leftSwitchClick 与 rightSwitchClick 方法看名字就能知道这是switch模式下,左边按钮与右边按钮的点击事件。
_cancle 方法的英文意思时缓存,那我们就将它先理解为一些缓存操作相关的方法。
touchStart、touchMove、touchEnd 方法,我想看名字你们也应该猜到了这是移动端相关的手指触摸事件。
enter、leave方法,这两个我们之前是不是在哪见过。是的它们就是滚动主容器上绑定的鼠标事件。忘记的可以回过头去,在讲解template结构上看一下。
_move方法,名字是移动的意思。那我们就先将它理解为移动相关的函数。
_initMove方法,这个就是我们刚才mounted中的初始化函数。我们等下就会详细讲解。
_dataWarm方法,这个看意思是数据警告,如果展示还不理解先放一下。等下我们回头再看。
_startMove与_stopMove方法,这个也根据名字见他们风别理解为开始移动与停止移动的方法。
看到这里你可能会有这样的感触,原来命名方法或属性的时候。名字取得与功能相对贴进一点是多么的友好!闲话少说,我们进入主要的部分。
_initMove初始化方法源码分析(主要)
从全局来看,_initMove方法使用vue中的nextTick方法进行异步回调处理。然后在器方法中获取一些基础的属性,根据这些属性进行一些滚动之前的初始化。
_dataWarm方法与清空copyHtml的操作。这里有遇到了这个_dataWarm方法,心急的同学可能会马上去看一下里面的内容。不过我喜欢先读完一整个方法在去看一些它使用过而我没有了解的方法。那我们就继续往下走。
第一个方框部分: 这段代码的意思是,如果为水平滚动这通过ref属性来获取整个容器的宽高与插入进来dom容器的宽度。代码如下
this.height = this.$refs.wrap.offsetHeight
this.width = this.$refs.wrap.offsetWidth
let slotListWidth = this.$refs.slotList.offsetWidth
再根据autoPlay属性来修正滚动容器的宽度与滚动父容器的宽度,最后再根据slotListWidth 设置内容的实际宽度realBoxWidth。
autoplay属性来动态切换动画的样式。
scrollSwitch来判断是否可以滚动,如果可以就重新开启一个计时器。并将插入进来的dom使用$ref的方式获取其中的dom保存下来。然后通过一个settimeout来实现异步获取父滚动容器的高度。并调用滚动函数开始滚动。否则就会调用_cancle方法做一些缓存处理。并初始化yPos与xPos属性。
看到这里肯定有一些心急的同学把这个_cancle方法看完了,那我这里也回头看下这个处理缓存的函数吧。
这里面只有一行代码,就是清除使用requestAnimationFrame方法所造成的缓存。那我们在回头看一下_dataWarm方法。
看的出来作者还是很贴心的。接下来就是核心的_move方法了。
_move滚动方法源码分析(主要)
由于_move方法的代码比较多,所以这里将逐层讲解。这样也可以更清晰的看出该部分的结构。便于我们更好的阅读源码。
这里先看最外层,作者标出的注释也比较贴心。所以这里我们理解起来也很容易。他先使用了一个开关变量来模拟鼠标的hover事件,实现停止与滚动的切换。接着就是滚动之前的缓存清理操作。
这里他使用了requestAnimationFrame方法来实现滚动的效果,在requestAnimationFrame中的这个回调方法就是滚动的主函数。在这个函数后还使用bind方法将this保存了下来,以防回调函数中的this指向问题。
不得不提一下要注意使用requestAnimationFrame与settimeout来实现动画的区别了。具体的区别也是比较容易搜到的。我也提供了一下相关的质料。
setTimeout, setInterval 与 requestAnimationFrame 隐藏的各种坑
这部分就是我们重点关注的对象,来看看这里可是怎么实现的。
函数的开始通过绑定this来获取滚动容器的真实宽度与高度,滚动的方向,单步停止等待时间、步长。在跟据这些属性做出下面对应的一些逻辑操作(根据滚动方向与单步滚动模式下做出的一些对应的逻辑操作)。
先展开框出来的第一部分。截图中我没有添加注释,因为这里也是比较简单。我们先把带有注释的上与左一同理解,下与右一同理解。他们只有滚动变化的属性(xPos与yPos)不同其他逻辑都是一模一样的。那这里我就把带有注释上与下拿出来详细解释一下。
上:如果y轴方向滚动距离的绝对值(yPos的滚动距离)大于等于父容器高度的一半,就将y轴滚动的位置赋值为起始位置。再触发外部的ScrollEnd方法。然后就是根据滚动步长来计算y轴滚动的位置。
下:如果y轴方向滚动距离的绝对值(yPos的滚动距离)小于等于滚动的起始位置时,就将y轴滚动的位置赋值为父容器高度的一半的负值。再触发外部的ScrollEnd方法。然后就是根据滚动步长来计算y轴滚动的位置。
我们来根据这张图理解一下。假设页面刚好滚动到如图这种情况,然后继续向上滚动,刚好滚动到我们插入进来的dom完全隐藏时(刚好滚动了一个dom的高度)。他就会将滚动的位置初始为起始的位置。如此往复,就达到了循环滚动的效果。
向下滚动也可以想象一下滚动到当前位置时,我们插入进来的dom上面没有dom了,所以就将位置向前了一个dom容器的高度。在如此往复。
接下来就是单步滚动模式下的逻辑了。
这里也是非常简单的。先将单步滚动的定时器清除掉。在进入判断时宽度的单步滚动模式还是高度单步滚动模式。看判断是宽度还是高度单步滚动的两个if判断中的内容,是不是一模一样。所以不要怕干就完了。
同样的先判断realSingleStopHeight或realSingleStopWidth是否存在,再通过滚动的距离对realSingleStopHeight或realSingleStopWidth取余,看是否小于步长。如果小于就进入单步定时器进行等待滚动。否则直接调用move方法进行滚动。是不是很简单。
到这里我们对vue-seamless-scroll中的核心源码就全部讲解完毕了。如果有同学还对它其他部分的源码感兴趣,可以根据这个分析思路继续去了解一下。相信你们的能力一定可以的。
五、 个人插件推荐
在这篇文章的最后我也带上一点私货。那就是我自己实现的一个循环滚动插件。这个插件虽然没有vue-seamless-scroll这个功能丰富,但他将vue-seamless-scroll不足的地方都弥补了一下。包括复制出来dom的点击事件,及数据的响应依赖都保存了下来。还增加了鼠标的滚轮模式。避免与滚动中的数据交互时的尴尬问题(例:滚动过去的数据要在等下一轮滚动才能交换)。
插件地址:https://www.npmjs.com/package/@david-j/vue-j-scroll 效果图:
图中鼠标悬浮再滚动容器上仍何以使用滚轮控制滚动。如果想与滚动过去的某一条数据交互就不需要再等一个轮回了。是不是解决了某些同学的燃眉之急。如果感兴趣并且使用了该插件,还请各位同学多多反馈。我会积极的对插件中的问题及时修复。功能上也会相对做出扩展的。
结语
好了这篇文章到这里就完全结束了,文章中我的一些个人看法较多。
vue-seamless-scroll使用中遇到关于click的问题.
Vue 中 Key的详细解释.
setTimeout, setInterval 与 requestAnimationFrame 隐藏的各种坑