日常开发中,触摸滑动成为移动应用程序中不可或缺的交互方式。为了提供流畅、自然和高效的滑动体验,许多开发者选择使用开源库来简化开发过程。其中,BetterScroll 因其强大的功能和灵活的定制能力而备受关注。本文将深入探讨 BetterScroll 的工作原理、功能特点以及如何将其应用于实际项目中。
实现效果如下:
一.介绍
BetterScroll2.0 的官网地址是:better-scroll.github.io/docs/zh-CN/
它不依赖于任何框架,你可以在vue和react项目里面都可以使用。
二.原理
滚动原理:当子元素宽度或者高度大于父元素,并且父元素设置overflow:auto的时候,页面就会出现横向或者纵向的滚动条。如果我们把滚动条隐藏了,然后在子元素支持拖动事件,是不是就能达到想要的效果。
三.使用
BetterScroll有好几个版本,新版本下载是:
npm install @better-scroll/core --save
yarn add @better-scroll/core
使用:
import BScroll from '@better-scroll/core'
let wrapper = document.getElementById("wrapper")
let bs = new BScroll(wrapper, {
//配置项
})
在react里面,我们将let wrapper = document.getElementById("wrapper")
利用 ref
替换。
具体的配置项你可以查看地址: better-scroll.github.io/docs/zh-CN/…
需要什么配置什么就好了。
import BScroll from 'better-scroll'
const scroll = new BScroll(scrollContaninerRef.current, {
scrollX: direction === "horizental",
scrollY: direction === "vertical",
probeType: 3,
click: click,
bounce: {
top: bounceTop,
bottom: bounceBottom
}
});
四.封装-react
我们封装的Scroll组件
import React, { forwardRef, useState, useEffect, useRef, useImperativeHandle } from "react";
// import BScroll from "better-scroll";
import BScroll from '@better-scroll/core';
import './index.scss';
const Scroll = forwardRef((props, ref) => {
const { direction = 'vertical', click = true, refresh = true, bounceTop = true, bounceBottom = true } = props;
const { pullUp, pullDown, onScroll } = props;
const [bScroll, setBScroll] = useState();
const scrollContaninerRef = useRef();
useEffect(() => {
const scroll = new BScroll(scrollContaninerRef.current, {
scrollX: direction === "horizental",
scrollY: direction === "vertical",
probeType: 3,
click: click,
bounce: {
top: bounceTop,
bottom: bounceBottom
}
});
setBScroll(scroll);
return () => {
setBScroll(null);
};
}, []);
useEffect(() => {
if (!bScroll || !onScroll) return;
bScroll.on('scroll', (scroll) => {
onScroll(scroll);
});
return () => {
bScroll.off('scroll');
};
}, [onScroll, bScroll]);
useEffect(() => {
if (!bScroll || !pullUp) return;
bScroll.on('scrollEnd', () => {
// 判断是否滑动到了底部
if (bScroll.y <= bScroll.maxScrollY + 100) {
pullUp();
}
});
return () => {
bScroll.off('scrollEnd');
};
}, [pullUp, bScroll]);
useEffect(() => {
if (!bScroll || !pullDown) return;
bScroll.on('touchEnd', (pos) => {
// 判断用户的下拉动作
if (pos.y > 50) {
pullDown();
}
});
return () => {
bScroll.off('touchEnd');
};
}, [pullDown, bScroll]);
useEffect(() => {
if (refresh && bScroll) {
bScroll.refresh();
}
});
useImperativeHandle(ref, () => ({
refresh() {
if (bScroll) {
bScroll.refresh();
bScroll.scrollTo(0, 0);
}
},
getBScroll() {
if (bScroll) {
return bScroll;
}
}
}));
return (
<div className="scroll-container" ref={scrollContaninerRef}>
{props.children}
</div>
);
});
export default Scroll;
使用
import React, { useRef, useEffect, memo } from 'react';
import Scroll from '@/components/Scroll';
import './index.scss';
function Horizen(props) {
const { list, oldVal, title } = props;
const { handleClick } = props;
const Category = useRef(null);
// 加入初始化内容宽度的逻辑
useEffect(() => {
let categoryDOM = Category.current;
let tagElems = categoryDOM.querySelectorAll("span");
let totalWidth = 0;
Array.from(tagElems).forEach(ele => {
totalWidth += ele.offsetWidth;
});
categoryDOM.style.width = `${totalWidth}px`;
}, []);
return (
<Scroll direction={"horizental"}>
<div ref={Category}>
<div className='song-list'>
<span>{title}</span>
{
list.map((item) => {
return (
<span
key={item.key}
className={`list-item ${oldVal === item.key ? 'selected' : ''}`}
onClick={() => handleClick(item.key)}>
{item.name}
</span>
);
})
}
</div>
</div>
</Scroll>
);
}
export default memo(Horizen);
五.封装vue
<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>
.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;
}
}
}
}
六.常见的问题汇总
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'
})