一、写在前面
在日常开发中,触摸滑动成为移动应用程序中不可或缺的交互方式。为了提供流畅、自然和高效的滑动体验,许多开发者选择使用开源库来简化开发过程。其中,BetterScroll 因其强大的功能和灵活的定制能力而备受关注。本文将深入探讨 BetterScroll 的工作原理、功能特点以及如何将其应用于实际项目中。
BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的iscroll的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。
BetterScroll 是使用纯 JavaScript 实现的,这意味着它是无依赖的。
二、滚动原理
在介绍BetterScroll之前,我们先来看一下浏览器的滚动原理:浏览器的滚动条大家都会遇到,当页面内容的高度超过视口高度的时候,会出现纵向滚动条;当页面内容的宽度超过视口宽度的时候,会出现横向滚动条。也就是当我们的视口展示不下内容的时候,会通过滚动条的方式让用户滚动屏幕看到剩余的内容。
BetterScroll 也是一样的原理,我们可以用一张图更直观的感受一下:
绿色部分为 wrapper,也就是父容器,它会有固定的高度。黄色部分为 content,它是父容器的第一个子元素,它的高度会随着内容的大小而撑高。那么,当 content 的高度不超过父容器的高度,是不能滚动的,而它一旦超过了父容器的高度,我们就可以滚动内容区了,这就是 BetterScroll 的滚动原理。
三、安装
1、npm
npm install @better-scroll/core --save
// or
yarn add @better-scroll/core
接下来就可以在代码中引入了,webpack(opens new window)等构建工具都支持从 node_modules 里引入代码:
import BScroll from '@better-scroll/core'
如果是 commonjs 的语法,如下:
var BScroll = require('@better-scroll/scroll')
2、script加载
BetterScroll 也支持直接用 script 加载的方式,加载后会在 window 上挂载一个 BScroll 的对象。
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.js"></script>
<!-- minify -->
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.min.js"></script>
let wrapper = document.getElementById("wrapper")
let bs = new BScroll(wrapper, {})
3、具备所有插件能力的 BetterScroll
npm install better-scroll --save
// or
yarn add better-scroll
import BetterScroll from 'better-scroll'
let bs = new BetterScroll('.wrapper', {})
也可以通过 CDN 加载。
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.js"></script>
<!-- minify -->
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js"></script>
let bs = BetterScroll.createBScroll('.wrapper', {})
四、使用
1、基础滚动
如果你只需要一个拥有基础滚动能力的列表,只需要引入 core。
import BScroll from '@better-scroll/core'
let bs = new BScroll('.wrapper', {
// ...... 详见配置项
})
2、增强型滚动
如果你需要一些额外的 feature。比如 pull-up,你需要引入额外的插件,详情查看插件(https://better-scroll.github.io/docs/zh-CN/plugins/)。
import BScroll from '@better-scroll/core'
import Pullup from '@better-scroll/pull-up'
// 注册插件
BScroll.use(Pullup)
let bs = new BScroll('.wrapper', {
probeType: 3,
pullUpLoad: true
})
3、全能力的滚动
如果你觉得一个个引入插件很费事,我们提供了一个拥有全部插件能力的 BetterScroll 包。它的使用方式与 1.0 版本一模一样,但是体积会相对大很多,推荐按需引入。
import BScroll from 'better-scroll'
let bs = new BScroll('.wrapper', {
// ...
pullUpLoad: true,
wheel: true,
scrollbar: true,
// and so on
})
注意:BetterScroll 2.X 里面,我们将 1.X 耦合的 feature 拆分至插件,以达到按需加载、减少包体积的目的。因此,@better-scroll/core 只提供了最核心的滚动能力。如果想要实现上拉加载、下拉刷新的功能,你需要使用对应的插件。
五、起步
BetterScroll 最常见的应用场景是列表滚动,我们来看一下它的 html 结构。
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
<!-- 这里可以放一些其它的 DOM,但不会影响滚动 -->
</div>
上面的代码中 BetterScroll 是作用在外层 wrapper 容器上的,滚动的部分是 content 元素。这里要注意的是,BetterScroll 默认处理容器(wrapper)的第一个子元素(content)的滚动,其它的元素都会被忽略。
最简单的初始化代码如下:
import BScroll from '@better-scroll/core'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)
BetterScroll 提供了一个类,实例化的第一个参数是一个原生的 DOM 对象。当然,如果传递的是一个字符串,BetterScroll 内部会尝试调用 querySelector 去获取这个 DOM 对象。
六、核心滚动
在 BetterScroll 2.0 的设计当中,我们抽象了核心滚动部分,它作为 BetterScroll 的最小使用单元,压缩体积比 1.0 小了将近三分之一,往往你可能只需要完成一个纯粹的滚动需求,那么你只需要引入@better-scroll/core这一个库。
BetterScroll 有多种滚动模式。下面我们介绍一下BetterScroll在vue3中的使用方法。
1、垂直滚动
<template>
<div class="scroll-container">
<div class="scroll-wrapper" ref="scroll">
<div class="scroll-content">
<div
class="scroll-item"
v-for="(item, index) in 100"
:key="index"
@click="clickHandler(item)"
>
第{{ item }}个
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import BScroll from '@better-scroll/core'
const scroll = ref()
const bs = ref()
const init = () => {
bs.value = new BScroll(scroll.value, {
// 1. probeType 为 0,在任何时候都不派发 scroll 事件,
// 2. probeType 为 1,仅仅当手指按在滚动区域上,每隔 momentumLimitTime 毫秒派发一次 scroll 事件,
// 3. probeType 为 2,仅仅当手指按在滚动区域上,一直派发 scroll 事件,
// 4. probeType 为 3,任何时候都派发 scroll 事件,包括调用 scrollTo 或者触发 momentum 滚动动画
probeType: 3,
click: true
})
bs.value.on('scrollStart', () => {
console.log('scrollStart-')
})
bs.value.on('scroll', ({ y }: { y: number }) => {
console.log('scrolling-', y)
})
bs.value.on('scrollEnd', (pos: { x: number, y: number }) => {
// {x: 0, y: -792},滚动结束时容器相对x轴y轴坐标
console.log(pos)
})
}
const clickHandler = (item: number) => {
window.alert(item)
}
onMounted(() => {
init()
})
onBeforeUnmount(() => {
bs.value.destroy()
})
</script>
<style lang="scss">
.scroll-container {
.scroll-wrapper {
height: 600px;
position: relative;
overflow: hidden;
.scroll-item {
height: 50px;
line-height: 50px;
font-size: 24px;
font-weight: bold;
border-bottom: 1px solid #eee;
text-align: center;
&:nth-child(2n) {
background-color: #f3f5f7;
}
&:nth-child(2n+1) {
background-color: #42b983
}
}
}
}
</style>
2、水平滚动
我们还以上面代码为例只需要改动两个地方即可,第一在new BScroll里面添加scrollX: true属性,如下:
bs.value = new BScroll(scroll.value, {
probeType: 3,
click: true,
scrollX: true
})
第二,把style里面的css代码改成如下代码:
.scroll-container {
.scroll-wrapper {
position: relative;
width: 90%;
margin: 80px auto;
white-space: nowrap;
border: 3px solid #42b983;
border-radius: 5px;
overflow: hidden;
.scroll-content {
display: inline-block;
.scroll-item {
height: 50px;
line-height: 50px;
font-size: 24px;
display: inline-block;
text-align: center;
padding: 0 10px;
}
}
}
}
BetterScroll 实现横向滚动,对 CSS 是比较苛刻的。首先你要保证 wrapper 不换行white-space: nowrap,并且 content 的 display 是 inline-block。
效果如下:
是不是感觉非常的丝滑呢!
3、水平和垂直同时滚动
<template>
<div class="free-scroll-container">
<div class="free-scroll-wrapper">
<div class="scroll-wrapper" ref="wrapper">
<div class="scroll-content">
<div class="scroll-item" v-for="(item, index) in 100" :key="index">
第{{ item }}个
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import BScroll from '@better-scroll/core'
const wrapper = ref()
const bs = ref()
const init = () => {
bs.value = new BScroll(wrapper.value, {
freeScroll: true,
bounce: {
bottom: false,
left: false,
right: false,
top: false
}
})
}
onMounted(() => {
init()
})
</script>
<style lang="scss">
.free-scroll-container {
position: fixed;
width: 100%;
height: 100%;
.free-scroll-wrapper {
position: relative;
width: 100%;
height: 100%;
border: 1px solid rgb(96, 108, 113);
box-sizing: border-box;
.scroll-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
.scroll-content {
background-color: #efeff4;
width: 1500px;
.scroll-item {
height: 50px;
line-height: 50px;
font-size: 24px;
font-weight: bold;
border-bottom: 1px solid #eee;
text-align: center;
&:nth-child(2n) {
background-color: #f3f5f7;
}
&:nth-child(2n+1) {
background-color: #42b983
}
}
}
}
}
}
</style>
当然,还有很多其它的用法,更多用法参见https://better-scroll.github.io/docs/zh-CN/。
七、常见问题
1、为什么 BetterScroll 初始化不能滚动?
BetterScroll 滚动原理是 content 元素的高度/宽度超过 wrapper 元素的高度/宽度。而且,如果你的 content 元素含有不固定尺寸的图片,你必须在图片加载完之后,调用 refresh() 方法来确保高度计算正确。还存在一种情况是页面存在表单元素,弹出键盘之后,将页面的视口高度压缩,导致 bs 不能正常工作,依然是调用 refresh() 方法。
2、为什么 BetterScroll 区域的点击事件无法被触发?
BetterScroll 默认会阻止浏览器的原生 click 事件。如果你想要 click 事件生效,BetterScroll 会派发一个 click 事件,并且 event 参数的 _constructed 为 true。配置项如下:
import BScroll from '@better-scroll/core'
let bs = new BScroll('./div', {
click: true
})
3、为什么我的 BetterScroll 监听 scroll 钩子,监听器不执行?
BetterScroll 通过 probeType 配置项来决定是否派发 scroll 钩子,因为这是有一些性能损耗的。probeType 为 2 的时候会实时的派发事件,probeType 为 3 的时候会在 momentum 动量动画的时候派发事件。建议设置为 3。
import BScroll from '@better-scroll/core'
let bs = new BScroll('./div', {
probeType: 3
})
4、slide 用了横向滚动,发现在 slide 区域纵向滚动无效?
如果想要保留浏览器的原生纵向滚动,需要如下配置项:
import BScroll from '@better-scroll/core'
let bs = new BScroll('./div', {
eventPassthrough: 'vertical'
})