better-scroll插件的介绍及使⽤
在我们⽇常的移动端项⽬开发中,处理滚动列表是再常见不过的需求了,可以是竖向滚动的列表,也可以是横向的,⽤better-scroll可以帮助我们实现这个
什么是 better-scroll
better-scroll 是⼀个移动端滚动的解决⽅案,它是基于 iscroll 的重写,它和 iscroll 的主要区别在。better-scroll 也很强⼤,不仅可以做普通的滚动列表,还可以做轮播图、picker 等等。
better-scroll的滚动原理
不少同学可能⽤过 better-scroll,出现最多的问题是:
我的 better-scroll 初始化了, 但是没法滚动。
不能滚动是现象,我们得搞清楚这其中的根本原因。在这之前,我们先来看⼀下浏览器的滚动原理:
浏览器的滚动条⼤家都会遇到,当页⾯内容的⾼度超过视⼝⾼度的时候,会出现纵向滚动条;当页⾯内容的宽度超过视⼝宽度的时候,会出现横向滚动条。也就是当我们的视⼝展⽰不下内容的时候,会通过滚动条的⽅式让⽤户滚动屏幕看到剩余的内容。
那么对于 better-scroll 也是⼀样的道理,我们先来看⼀下 better-scroll 常见的 html 结构:
<div class="wrapper">
js实现轮播图最简代码<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
</div>
为了更加直观,我们再来看⼀张图:
绿⾊部分为 wrapper,也就是⽗容器,它会有固定的⾼度。黄⾊部分为 content,它是⽗容器的第⼀个⼦元素,它的⾼度会随着内容的⼤⼩⽽撑⾼。那么,当 content 的⾼度不超过⽗容器的⾼度,是不能滚动的,⽽它⼀旦超过了⽗容器的⾼度,我们就可以滚动内容区了,这就是 better-scroll 的滚动原理。
那么,我们怎么初始化 better-scroll 呢,如果是上述 html 结构,那么初始化代码如下:
import BScroll from 'better-scroll'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper, {})
better-scroll 对外暴露了⼀个 BScroll 的类,我们初始化只需要 new ⼀个类的实例即可。第⼀个参数就是我们 wrapper 的 DOM 对象,第⼆个是⼀些配置参数,具体参考 。
better-scroll 的初始化时机很重要,因为它在初始化的时候,会计算⽗元素和⼦元素的⾼度和宽度,来决定是否可以纵向和横向滚动。因此,我们在初始化它的时候,必须确保⽗元素和⼦元素的内容已经正确渲染了。如果⼦元素或者⽗元素 DOM 结构发⽣改变的时候,必须重新调⽤ fresh() ⽅法重新计算来确保滚动效果的正常。所以同学们反馈的 better-scroll 不能滚动的原因多半是初始化 better-scroll 的时机不对,或者是当 DOM 结构发送变化的时候并没有重新计算 better-scroll。
better-scroll 遇见 Vue
相信很多同学对 Vue.js 都不陌⽣,当 better-scroll 遇见 Vue,会擦出怎样的⽕花呢?
如何在 Vue 中使⽤ better-scroll
很多同学开始接触使⽤ better-scroll 都是受到了黄轶⽼师的⼀门教学课程——《Vue.js⾼仿饿了么外卖App》 的影响。在那门课程中,我们把 better-scroll 和 Vue 做了结合,实现了很多列表滚动的效果。在 Vue 中的使⽤⽅法如下:
<template>
<div class="wrapper" ref="wrapper">
<ul class="content">
<li>...</li>
...
</ul>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
mounted() {
this.$nextTick(() => {
this.scroll = new Bscroll(this.$refs.wrapper, {})
})
}
}
</script>
Vue.js 提供了我们⼀个获取 DOM 对象的接⼝—— vm.$refs。在这⾥,我们通过了 this.$refs.wrapper
访问到了这个 DOM 对象,并且我们在 mounted 这个钩⼦函数⾥,this.$nextTick 的回调函数中初始化 better-scroll 。因为这个时候,wrapper 的 DOM 已经渲染了,我们可以正确计算它以及它内层 content 的⾼度,以确保滚动正常。
这⾥的 this.$nextTick 是⼀个异步函数,为了确保 DOM 已经渲染,感兴趣的同学可以了解⼀下它的内部实现细节,底层⽤到了MutationObserver 或者是 setTimeout(fn, 0)。其实我们在这⾥把 this.$nextTick 替换成 setTimeout(fn, 20) 也是可以的(20 ms 是⼀个经验值,每⼀个 Tick 约为 17 ms),对⽤户体验⽽⾔都是⽆感知的。
异步数据的处理
在我们的实际⼯作中,列表的数据往往都是异步获取的,因此我们初始化 better-scroll 的时机需要在数据获取后,代码如下:
<template>
<div class="wrapper" ref="wrapper">
<ul class="content">
<li v-for="item in data">{{item}}</li>
</ul>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
data() {
return {
data: []
}
},
created() {
requestData().then((res) => {
this.data = res.data
this.$nextTick(() => {
this.scroll = new Bscroll(this.$refs.wrapper, {})
})
})
}
}
</script>
这⾥的 requestData 是伪代码,作⽤就是发起⼀个 http 请求从服务端获取数据,并且这个函数返回的是
⼀个 promise(实际项⽬中我们可能会⽤ axios 或者 vue-resource)。我们获取到数据的后,需要通过异步的⽅式再去初始化 better-scroll,因为 Vue 是数据驱动的, Vue 数据发⽣变化(this.data = res.data)到页⾯重新渲染是⼀个异步的过程,我们的初始化时机是要在 DOM 重新渲染后,所以这⾥⽤到了 this.$nextTick,当然替换成 setTimeout(fn, 20) 也是可以的。
为什么这⾥在 created 这个钩⼦函数⾥请求数据⽽不是放到 mounted 的钩⼦函数⾥?因为 requestData 是发送⼀个⽹络请求,这是⼀个异步过程,当拿到响应数据的时候,Vue 的 DOM 早就已经渲染好了,但是数据改变 —> DOM 重新渲染仍然是⼀个异步过程,所以即使在我们拿到数据后,也要异步初始化 better-scroll。
数据的动态更新
我们在实际开发中,除了数据异步获取,还有⼀些场景可以动态更新列表中的数据,⽐如常见的下拉加载,上拉刷新等。⽐如我们⽤better-scroll 配合 Vue 实现下拉加载功能,代码如下:
<template>
<div class="wrapper" ref="wrapper">
<ul class="content">
<li v-for="item in data">{{item}}</li>
</ul>
<div class="loading-wrapper"></div>
</div>
</template>
<script> import BScroll from 'better-scroll'
export default {
data() {
return {data: []}
}, created() {
this.loadData()
}, methods: {
loadData() {
requestData().then((res) => {
this.data = at(this.data)
this.$nextTick(() => {
if (!this.scroll) {
this.scroll = new Bscroll(this.$refs.wrapper, {})
('touchend', (pos) => {
// 下拉动作
if (pos.y > 50) {
this.loadData()
}
})
} else {
fresh()
}
})
})
}
}
}
</script>
这段代码⽐之前稍微复杂⼀些, 当我们在滑动列表松开⼿指时候, better-scroll 会对外派发⼀个 touchend 事件,我们监听了这个事件,并且判断了 pos.y > 50(我们把这个⾏为定义成⼀次下拉的动作)。如果是下拉的话我们会重新请求数据,并且把新的数据和之前的data 做⼀次 concat,也就更新了列表的数据,那么数据的改变就会映射到 DOM 的变化。需要注意的⼀点,这⾥我们对 this.scroll 做了判断,如果没有初始化过我们会通过 new BScroll 初始化,并且绑定⼀些事件,否则我们会调⽤ fresh ⽅法重新计算,来确保滚动效果的正常。
这⾥,我们就通过 better-scroll 配合 Vue,实现了列表的下拉刷新功能,上拉加载也是类似的套路,⼀切看上去都是 ok 的。但是,我们发现这⾥写了⼤量命令式的代码(这⼀点不是 Vue.js 推荐的),如果有很多类似滚动的组件,我们就需要写很多类似的命令式且重复性的代码,⽽且我们把数据请求和 better-scroll 也做了强耦合,这些对于⼀个追求编程逼格的⼈来说,就不 ok 了。
scroll 组件的抽象和封装
因此,我们有强烈的需求抽象出来⼀个 scroll 组件,类似⼩程序的 scroll-view 组件,⽅便开发者的使⽤。
⾸先,我们要考虑的是 scroll 组件本质上就是⼀个可以滚动的列表组件,⾄于列表的 DOM 结构,只需要满⾜ better-scroll 的 DOM 结构规范即可,具体⽤什么标签,有哪些辅助节点(⽐如下拉刷新上拉加
载的 loading 层),这些都不是 scroll 组件需要关⼼的。因此,scroll 组件的 DOM 结构⼗分简单,如下所⽰:
<template>
<div ref="wrapper">
<slot></slot>
</div>
</template>
1
2
3
4
5
这⾥我们⽤到了 Vue 的特殊元素—— slot 插槽,它可以满⾜我们灵活定制列表 DOM 结构的需求。接下来我们来看看 JS 部分:
<script type="text/ecmascript-6">
import BScroll from 'better-scroll'
export default {
props: {
/*1 滚动的时候会派发scroll事件,会截流。
2 滚动的时候实时派发scroll事件,不会截流。
3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件 */
probeType: {
type: Number,
default: 1
},
// 点击列表是否派发click事件
click: {
type: Boolean,
default: true
},
// 是否开启横向滚动
scrollX: {
type: Boolean,
default: false
},
/
/ 是否派发滚动事件
listenScroll: {
type: Boolean,
default: false
},
// 列表的数据
data: {
type: Array,
default: null
},
/** * 是否派发滚动到底部的事件,⽤于上拉加载 */
pullup: {
type: Boolean,
default: false
},
/** * 是否派发顶部下拉的事件,⽤于下拉刷新 */
pulldown: {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论