「Vue项目」我是如何解决滚动组件&联动效果
前语
最近的一个项目做的是vue组件中的一个运用,「处理翻滚列表」,这个应该是很常见的需求了,在项目中遇到的痛点,难点,怎样一步步处理的,以及小细节一些优化。
学习某课的思路,仿QQ音乐效果,M 3 + } G记载一下,自己字母处理这个难题,共享给你们,「希望对你们X 8 / # 做移动端翻滚列表问题有所协助」
GitHub库房
效果

从终究效果来看,结束了三个我的难点
-
榜首个就是右侧快速进口,点击一个字母跳7 z v转到相应的部分 -
左上角的那个title是跟随字母一起批改内容的 -
向下翻滚的话,会随时改写右侧快速路口以及左上角字母title
接下来就是一步步去结束,优化上面的效果
第三方库介绍
better-scroll 移动端翻滚的处理计划
vue-lazyload 图片懒加载
根本上结束上面的效果就是根据这两个第三方库
「better-scroll根本运用」
经常会遇到的问题就是初始化了,「仍是不能翻滚」。那么对于这个而言,我最近用到一些经验是什么呢?
我们先看常见的html效果J ? $ Y M
&l7 N ! O c V wt;div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>7 y G
...
</ul>
<!-- you can put some oa E [ R m m w 3 Tther DOMs here, it won't affect th% t j K 7 e ee scrolling
</div>
翻滚的原理是什么

wrapper是父容器,m / J S它必定要有「固定高度」,content是内容区域,它是父元素的榜首: L $ S J 8 ) h个元素,它content会跟着内容的巨细撑开而撑高,只需这个高度大于wrapper父容器高度时,才会出现翻滚,也就是它的原理。
那么我们怎样去初始化呢
import BScroll from '@better-scroll/core'
let wrapper. 7 4 = document.querySelector('.wrappek x Br')
let scroll = new~ 2 } z w ) f BScroll(wrapper,{})
//{}配备一些信息
点这儿有文档
接下来就l 5 M开始把
从0到1结束
scroll组件
这个scroll组件是子组件,也可以算是个base组件,结束日常翻滚3 / A a o ) |的效果
<template>
<m O S Z =div ref="wrapper">
<slot></slot>
</div>
</template>
<scriV r y @pt type="text/ecmascript-6">
import BScro- { &ll from 'better-scroll'
export default {
props; 8 ] ] T v - p: {
probeType: {
type: Number,
default: 1
},2 H j 8
click: {
type: Boolean,
default: true
},
lisQ C I e ,tenScroll: {
type: Boolean,
default: false
},
data: {
type: Array,
default: null
},
pullup: {
type: Boolean,
default: false
},
beforeScroll: {
type: Boolean,
dea 9 : 6fault: false
},
refreshDelay: {
type: Number,
default: 20
}
},
mounted7 s 8 p :() {
setTimeout(() => {
this._initScroll()
}J H 9 E D, 20)
},
methods: {
// 初始化Scroll
_initScroll() {
// 判别是否初始化
if (!this.$refs.wrappers F .) {N v Q M 6 a g U
return
}
// 调用Scroll实例,体现可以滑动
this.scroll = new BScroll(this.$refs.wra+ | E # %pper, {
pi 9 $ i Q v = irobeType: this.probeType,
click: this.click
})
if (this.listenScroll) {
let me = this
this.scroll.on('scroll', (pos) =&gC R P T U , 4 U (t; {
me.$emit('i ~ [scroll', pos)
})
}
if (th! t 8 f pis.pullup) {
this.scroll.V D m T *on('scrollEny m D g a ld',. X r g () => {
if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
this.$emit('scrollToEnd')
}
})
}
if (this.beforeScroll) {
this.scroll.o& 6 { { O W ( $n('R 6 ~ ObeforeScrollStart', () => {
this.$emit('beforeScroll')
})
}
},
disable() {
this.scroll && this.scroll.S c ]disable()
},
enable() {
this.scroll && this.scroll.enable()
},
refresh() { // 改写sci ! Z sroll,从头核算高度
this` P R.scroll && this.scroll.refresh()
},
scrollTo() {
this.scroll && this.scrn ^ Z $ Qoll.o L z .scrw C a 9 m J vollTo.apply(this.scroll,@ ` q 3 arguments)
},
scrollTo! 9 k 1Element() {
this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
}
},
watch:6 = Y - ` X ( {
// 监听到数据的改动,就会从头去refresh数据,从头去核算照应* B g v的数据
data() {
setTimeout(() => {
this.refresh()
}, this.refreshDelay)
}
}
}
</scrc E b K .ipt>
<style scoped lang="stylus" rel="stylesheet/stylus">
&$ o a 7 y 5 </style>
在父组件中] , 3 N @ u导入即可
结束列表翻滚
listview组件导入
<template>
<scroll
:listen-scroll="listenScroll"
:probe-type="probeType"
:data="daU J % W 4 t Hta"
class="listview"
ref="listview"
>
<ul&Y A O a Kgt;
&lI _ [ f Ut;li v-for="(group,index) in data, D , v M %" class="list-group" ref="listGroup" :key=; h = T 6"indexN E t P a 4 I ?">
<h2 class="list-group-title"&K ) y & cgt;{{group.title}}</hK D d u j T d x2>
<uL>
<li
@click="selectItem(item)"
v-for="(item, index) in group.items"
class="list-group-item"
:key="index"
>
<img claH B T ! * wss="avatar" v-lazy="item.avatar" />
<span class="d J V ) C y + Z Yname">{{item.name}}</span>
</li>
</uL>
&l: - Y # y * f R t;/li>
</ul&g! A 3 t;
</scroll>
</template>
然后导入scroll组件即可,看看效果

上面在listview组件中导入scroll组件,结束根本的列表翻滚效果,接下来,完善一步一步效果吧。
右侧快速进口
<div
class="list-shortcut"
@t8 ` b g ? 7 c 8ouchstart="onShortcutTouchStart"
@touchmove.stop.prevent="onShortcutTouchMove: ~ m Y #"
>
<!-- data-index便当获取一个列表中的index --> 3 x h P x I;
<ul>
&t ~ 6lt;li
v-for="(item, index) in short; ; 9 7 ecutList"
:dataE l ^ Q 2 $-index=b w x X l @ S ( 9"index"
class="item"
:class="{'current':currentIndex=A ] , b H==index}"
:key="inI G t h + udexv l g ?"
>{{item}}v h ( d %</li>
</uZ 2 j , u E j cl>
</div>
点2 F Y ` 4击s } ` $ m [ i A右侧快速路口的话,会跳转到相应的title去,运用的方法就是
scrollElement
scrollToElement(el, time, offsetXW – ! V # F Q, offsetY, easing)
「这个方法很便当的处理了我们榜首个难点」,现在就差获取右侧快速t h $ 6路口的索引$ P t 6 O 2 p r值了
给每一个li增加一个data-index特色称号,值为index下
:z J fdata-index="indexW u T q o V g"
这g & [姿势每次就可以获取其时的索引值
有了索4 + B b P J引值,我们就可以直接调用srcollToElement()方法,结束左面的跳转效果。
this.$refs.listview.scrollToElement(this.$re1 w 7 vfs.listGroup[index], 0);z E K t q # ; J
// 这个index就是获取到下标索引值,然后通过这个
// 这个第二个参数是翻滚的动画的时刻,我们默认为0就行U u L u R O ? Z i,文档上面也有专门的说明,可以去看看。
我们看看效果吧下

接下来结束「tot R h = A & h duchMove工作」,我们绑} N W r N ` R `定到diC S % * * f 6 # v上
@touchm= 4 Y k ! l @ Fove.stop.preved 5 X , F Z * Ynt="onSO z $ + I {hortcutTouchMove"
// 两个修饰符阻止冒泡以及默认的工作
思路
-
首先要监听to D V | @ quchStartO / 7工作一开始锚点,也就是anc F @ 5chorIndex,还有保存e.touches[0].pageY, y轴上的方位信息,记作y1 -
监听touchuMove工作,保存y轴距离,记为y2,这个时分y2-yJ 5 ,1就是y轴上的距离改动dataChange– ! k ( ? c S b -
将这个距离dataChange除以高度,这儿的高度,我选择的是每个li的content+padding高1 0 g a ^度,这个高度的话,正好是整个一个li元素高度,我觉得很合理,delta = dataChange/ANCHOR_HEIGHT -
终究一开始的anc* A ( 7 1 ; b *horIndex加上Y x Fdelta,就是最新的锚点,这个anchorIndex必定要取证,因为获取的可能是字符串。
看代码
onShortcutTouchStart(e)c r W ~ e R 7 B {
// 获取到右侧的列表索引值
let anchorIndex = getDa Q # r q ]ata(e.target, "index");
let firstTouch =z $ } . i e.touches[0];
this.touch.y1 = firstTouch.pageY; // 计入一开始y轴上的方位
this.touch.anchorIndex = anchorIndex; // 保存了每次点击的锚点
this._scs x z P arollTo(anchorI4 ; t v hndex);
},
// 监听的是TouchMove工作
onShortcutTouchMove(t * J c t Y we) {
let firstTouch = e.touches[0]9 N + x j 2 );
this.touch.y2 = firstTouch.pageY;
// 翻滚的两个差值 也就是y轴上的偏移
// 除以每个高度,这姿势的话,n ; z S - R就知道偏移了几个锚点
let delta = ((this.touch.y2 - this.touch.yX K L n % 9 Q $1) / ANCHOR_HEIGHT) | 0;
let ancho$ 7 s q R trIndex = parseInt(this.touch.anchorIndex) + delta;
this._scrollTo[ H I(anchorIndex);
},
效果怎样样呢,根本上点击和手势移动都较为完美的结束了。

左右联动
左右联动的效果指的是左面点击到某个区域,紧接着右侧快速路口也跳转到相应方位,这儿其实指的就是高亮效果。
效果就是滑动列表,右侧的字母会相应的高亮,达到同步的效果,难点是什么呢?

从图片上面看,我们发现每个listGroup分组里面的成员是不固定的,所以我们怎I W $ d [ E样去获取到相应的currentIndex呢?
「我们可以获取到每次翻滚的距离,那怎样样去获取相应的currentIndex呢a = + t F $ , ,比方滑到K分组时,currentIndeT d `x是对应的下标?」
有个不错的思路:
-
我们去维护一个height[i]数组,该数组意义就是第! b m F I } # ? ni个分组的规划是height[i]~~heigth[i+1] -
那么我们获取到翻滚Y轴的距离,那么就可以承认它地点的规划,假设翻滚的距离在posY>height[i]&&= ~ u z l;posY<height[i+1],那么currentIndex就可以取值i,这姿势如同行。
那么我们依照上面的思路来完善吧
_calculateHeight() {
// 这个方法/ ; W & y # q z就是核算每个listGroup高度
this.listHeiB j Q ? r n bght = [];
const list = this.$refs.listGroup;
let height = 0;
this.listHeight.push(height);
for (let i = 0; i < list.length; i++) {
let item = list[i];
height += itE I S Sem.clientHeight;
this.listHeight.push(height);
}
},
这个listHeigth数据就是我们维护的第i个` f 5 @ h 1 5 b X分组的clientHeight距离
第二步,我们监控这个scrollY,这个变量标明的就是翻滚的距离
watch: {
// 每次去watch这个翻滚的距离,
scrollY(newY) {
const listHeight = this.q M ] GlistHeight;
// 当翻滚到顶部,newY>0
if (newY > 0) {
this.curo X g UrentY r S - U N @ +Index = 0;
return;
}
// 在中心部分翻滚
for (let i = 0; i < listHeight.length - 1; i++) {
let height1 = listHeight[i];
let height2 = listHei| 8 Ught[ib ^ 5 # e % w C H + 1];
if (-newY >= height1 && -newY < height2) {
this.currentIndex = i;
this.diff = height2 + newY;
return;
}
}
// 当翻滚到底部,且-newY大于终究一个4 L U } b G元素的上限
this.currentIndex = listHeight.length - 2;
},
这儿需求提示的就是,我们怎样去拿到这个scrollY翻滚C * J G距离呢?
说到这个,我们得看到scroll组件中,阅读它的API,会发现它供应了on方法,该方法可以去监听该「实例的钩子函数」,所以我们去「监听钩子函数scroll」
「scroll钩子函数」
-
参数:{Object} {x, y} 翻滚的实时坐标 -
触发机遇:翻滚过程中。
所以我们可以通过这个钩子来获取翻滚的实时坐标
if (thiZ s N Ys.listenScroll) {
let me = this
this.scroll.on('scroll', (pos) => {
// me指的就是实例
// 通过监听scroll工作,有一个回调,pos是一个目标,有x,y轴的具体距离
// 去派发一个scroll工作,这姿势外部也就是父4 ~ z n d p ~ R |组件可以拿到我们的pos
me.$emit('scroll', pos)
})
}
这姿势我们在子组件scrollH T A – m ~ d , ~中向外派发一个scroll工作,并且把「pos = ; c } {Object} {x, y} 翻滚的实时坐标」向外传递,这姿势的话,父组件通过@scroll=”scroll” 就可以拿到这个坐标pos
这姿势我们这个难点就处理了。
我们来看看效果
这姿势根本上问题就处理了,但是呢还会遇到一个问题?
probeType
-
类型:NumbeQ J t ! r 1r -
默认值:0 -
可选值:1、2、3 -
效果:有时分我们需求知道翻滚的方位。当W t 6 W ; r @ probeType 为 1 的时分,会非实时(屏幕滑动超越必定时刻后)派发scroll 工作;当 probeTypK ( R / [e 为 2 的时分,会在屏幕滑动的过程中实时的派发 scroll 工作;当 probeType 为 3 的时分,不仅在屏幕滑动的过程中,并且在 momentum 翻翻滚画运转过程中实时派发 scroll 工作。假设没有设置该值,其默认值为 0,即不派发I J L ) 8 ^ scroll 工作。
这个是文档上面的内容,我们可以看到这个配备项仍是很重要的,我们listview组件需求通过props向子组件传递probeType值,值为3,这姿势就可以「在i u L翻滚中实时派发 scroll 工作」。
<scroll :probe-type='3'></scroll&! B K D s N & , Hgt;
// 当然了,这个probeTyep会在data中拿到l f i O
总结
-
处理难点一,获取翻滚的实时方位,通过子组件scroll实例on方法,去「监听钩子函数scrolO @ l h $ 7l」,然后向外去派发一个scroll函数,并且把翻滚的距离传给父组件listview -
处理难点二,获取到翻滚Y轴的距离,就可以进一步去判别,它是落在哪一个listGroup中,也就是哪一个分组中,E 4 / E k这姿势就承认currentIndex。 -
通过watch监O ] l F ^ , 5 ( [听scrollY值,标明Y轴翻滚距离,产生改动时,8 R L J更新curo & 4 ? C ` g ,rentIndex。 -
有了currentIndex,在判别currentIndex === index,就可以结束高亮效果 -
还有一些BetterScroll 供应的8 – S P N } k UAPI -
比方refresh(),从头核算 BetterScroll,当 DOMN H 4 i 结构产生改动的时分务必要调用保证翻滚的效果正常 -
scrollToElement(el, tiD $ Z V &me, offsetX, offsetY, easing) 翻滚到指定( Z E x ] } b J k的目标元素 -
on(type, fn, context) 监听其时实例上的钩子函数。如:scroll、scrollEnd 等
-
-
还有一个收获就是用第三方API,「有问题必定要查文档」。
「❤️ 感谢我们」
假设你觉得这篇内容对你挺有有协助的话:
-
点赞支撑下吧,让更多的人也能看到这篇内容(保藏不点赞# ` ( , g 1 %,都是耍流氓 -_-)
-
欢迎– I O n在留言区与我共享你的主意,也欢迎你在留言区记载你的考虑过程。
-
觉得不错的话,也可以阅读TianTian近期整理的文章(感谢掘友的鼓舞与支撑):
-
「查缺补漏」送你18J m Q 3 } ! #道浏览器面试题(450+) -
「查缺补漏」送你 54 道 Javd ^ 6 p 4 & 8 P kaScript 面试题(500+) -
「算法与数据结构」链表的9个根本操作(150+) -
「小技巧」写给男同胞的Chrome DevTools调试小技巧,效率(210+) -
「浏览器作业原理」写给女友的秘籍-烘托流程篇(1.1W+字)(2F L S f 030+) -
「数组方法」从具体操作js数z ; y 7 V u n ; Y组到浅析v8中array.js(220+) -
「浏览器作业原理」写给女友的秘籍# 4 u y p 7 y-浏览器组成&amG w & F U g _ * 6p;网络请求篇(1.2W字)(240+)
-
本文运用 mdnice 排版