移动端-vue-BScroll用法教程
- 简介
- 官网
- 安装
- 使用
- 移动端滚动使用
- 移动端联合滚动实现懒加载
- 页面使用
- 扩展-什么是防抖和节流
- 相同点
- 区别
简介
BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件,是最接近与原生的滚动插件,兼容pc和移动端。
官网
https://better-scroll.github.io/docs/zh-CN/
安装
npm install better-scroll --save
使用
import BScroll from "better-scroll";let bs = new BScroll('.wrapper', {scrollbar: true,//是否显示滚动条bounce: true, //回弹动画click: true,//派发点击事件scrollX: false, //是否横向滚动
})bs.on("pullingDown", async () => {//下拉刷新 //当使用监听事件下拉刷新,但是pullingDown回调完函数,要在最后添加finishPullDown(); // 作用是事情做完,需要调用此方法告诉 better-scroll 数据已加载,否则下拉事件只会执行一次bs.finishPullDown();bs&& bs.refresh(); //重新计算 BetterScroll});bs.on("pullingUp", async () => {//上拉加载//上拉同理与下拉,否则上拉事件只会执行一次。bs.finishPullUp();bs&&bs.refresh(); //重新计算 BetterScroll});
移动端滚动使用
<template><divclass="scroll"ref="scrollRef"><div class="scroll-wrap"><divclass="pulldown"v-if="pullDownRefresh"><div v-show="beforePullDown"><span>继续下拉以刷新</span></div><div v-show="!beforePullDown"><div v-show="isPullingDown"><span>加载中</span></div><div v-show="!isPullingDown"><span>加载完成</span></div></div></div><slot></slot><divclass="pullup"v-if="pullUpRefresh"><divv-if="!isPullUpLoad && !loading"class="before-trigger"><span class="pullup-txt">{{ finished ? "暂无更多数据" : "上拉加载更多数据" }}</span></div><divv-elseclass="after-trigger"><span class="pullup-txt">加载中...</span></div></div></div></div>
</template><script lang="ts">
import { defineComponent, ref, onMounted, nextTick, watch } from "vue";
import BScroll from "better-scroll";export default defineComponent({name: "BetterScroll",props: {loading: {//加载type: Boolean,default: false,},refresh: {//是否刷新type: Boolean,default: false,},pullDownRefresh: {//是否下拉加载type: Boolean,default: false,},downRefresh: {//下拉加载方法type: Function,default: async () => {},},pullUpRefresh: {//是否上拉刷新type: Boolean,default: false,},upRefresh: {//上拉刷新方法type: Function,default: async () => {},},scrollX: {// 是否横向滚动type: Boolean,default: false,},finished: {// 是否滚动到底-数据加载完成type: Boolean,default: false,},bounce: {// 是否需要回弹动画 当滚动超过边缘的时候会有一小段回弹动画type: Boolean,default: true,},},setup(props, context) {const scrollRef = ref<HTMLElement | null>(null); //滚动父盒子const bs = ref<BScroll | null>(null); //滚动值const beforePullDown = ref(true); //显示继续下拉以刷新const isPullingDown = ref(false); //显示下拉加载中const isPullUpLoad = ref(false); //显示上拉加载中watch(() => props.refresh,async () => {if (props.refresh) {await nextTick(); //视图更新后setTimeout(() => {bs.value && bs.value.refresh();// 新查询数据后重新计算 BetterScroll}, 500);context.emit("update:refresh", false);// 将refresh状态改变 达到监听refresh的目的 为了重新计算BetterScroll}});// 完成下拉事件const finishPullDown = async () => {if (bs.value) {//当使用监听事件下拉刷新,但是pullingDown回调完函数,要在最后添加finishPullDown();// 作用是事情做完,需要调用此方法告诉 better-scroll 数据已加载,否则下拉事件只会执行一次bs.value.finishPullDown();bs.value && bs.value.refresh();setTimeout(() => {beforePullDown.value = true; //数据加载完成,显示初始上拉已刷新}, 100);}};onMounted(() => {if (scrollRef.value) {//Record的内部定义,接收两个泛型参数;Record后面的泛型就是对象键和值的类型//作用 :定义一个对象的 key 和 value 类型const options: Record<string, any> = {scrollbar: true, //是否展示滚动条bounce: props.bounce, //回弹动画click: true, //单独点击事件scrollX: props.scrollX, //当设置为 true 的时候,可以开启横向滚动。};if (props.pullDownRefresh) {// 当顶部下拉距离超过阈值 pullDownRefresh为true 执行pullingDown事件options.pullDownRefresh = true;}if (props.pullUpRefresh) {//当底部下拉距离超过阈值 pullUpLoad为true 执行pullingUp事件options.pullUpLoad = true;}bs.value = new BScroll(scrollRef.value, options); //创建实例bs.value.on("pullingDown", async () => {if (!props.pullDownRefresh) {//pullDownRefresh值为false 则不执行return;}beforePullDown.value = false; //显示加载中/加载完成isPullingDown.value = true; //加载中await props.downRefresh(); //下拉刷新数据 初始化数据isPullingDown.value = false; //加载完成finishPullDown();});bs.value.on("pullingUp", async () => {if (props.finished || !props.pullUpRefresh) {// 如果数据已加载完成,或pullUpRefresh为false 则不执行该事件return;}isPullUpLoad.value = true; //显示上拉加载await props.upRefresh(); //上拉获取数据if (bs.value) {//当使用监听事件下拉刷新,但是pullingUp回调完函数,要在最后添加finishPullUp();// 作用是事情做完,需要调用此方法告诉better-scroll 数据已加载,否则上拉事件只会执行一次bs.value.finishPullUp();bs.value.refresh();}isPullUpLoad.value = false;});}});return {scrollRef,isPullingDown,isPullUpLoad,beforePullDown,};},
});
</script><style lang="less" scoped>
.scroll {overflow: hidden;width: 100%;height: 100%;position: relative;z-index: 2;
}
.pulldown {position: absolute;width: 100%;padding: 20px;box-sizing: border-box;transform: translateY(-100%) translateZ(0);text-align: center;color: #999;
}
.pullup {padding: 20px;text-align: center;color: #999;
}
</style>
移动端联合滚动实现懒加载
<!-- loading:加载pullDownRefresh:是否下拉刷新pullUpRefresh:是否上拉加载refresh:是否改变刷新状态-用于强制重新计算BetterScroll值finished:是否完成状态downRefresh:下拉刷新事件-分页为一upRefresh:上拉加载事件-分页加一--><template><better-scroll:loading="loading":pullDownRefresh="true":pullUpRefresh="true"v-model:refresh="isRefresh":finished="finished":downRefresh="downRefresh":upRefresh="upRefresh"><templatev-for="(item, index) in data":key="index"><slotname="content":data="item":index="index"></slot></template></better-scroll>
</template><script lang="ts">
import { defineComponent, ref, watch, reactive, computed, SetupContext } from "vue";
import betterScroll from "./betterScroll.vue";
interface PropsType {order: string;apiService: (query: any, url?: string) => Promise<any>;query: object;refresh: boolean;
}interface PageQueryType {currentPage: number;pageSize: number;
}export default defineComponent({name: "scrollList",components: {betterScroll,},props: {// 数据调用接口apiService: {type: Function,default: new Promise(() => {}),},// 传参query: {type: [Object, Array],default: () => {},},// 是否更新BetterScroll位置refresh: {type: Boolean,default: false,},// 排序字段orderBy: {type: String,default: "captureDate",},},setup(props, context) {const { data, isRefresh, finished, downRefresh, upRefresh } = useSearch(props, context);return {data,isRefresh,finished,upRefresh,downRefresh,};},
});// 搜索
function useSearch(props: PropsType, context: SetupContext) {//context:SetupContext,即是setup函数的上下文 用于获取propsconst page = reactive<PageQueryType>({currentPage: 1,pageSize: 10,}); //初始化分页const data = ref([]); //数据const totalNum = ref<Nullable<number>>(null); //数据总条数const finished = computed(() => {return totalNum.value !== null ? data.value.length >= totalNum.value : false;}); //通过数据长度和总数据条数判断是否加载完const queryTimer = ref<Nullable<NodeJS.Timer>>(null);watch(() => props.query, //监听到查询条件async () => {isRefresh.value = true; //isRefresh设为true 因为有查询条件会重新加载数据 需要重新计算BetterScroll 使数据回到顶部位置// 采用防抖执行下拉刷新事件 多次快速查询只触发最后一个事件if (queryTimer.value) {clearTimeout(queryTimer.value);queryTimer.value = null;}queryTimer.value = setTimeout(async () => {data.value = [];await downRefresh();}, 50);},{deep: true,});watch(() => props.refresh,async () => {isRefresh.value = true;if (props.refresh) {data.value = [];context.emit("update:refresh", false);await downRefresh();}});// 下拉刷新事件const downRefresh = async () => {page.currentPage = 1;totalNum.value = null;data.value = await getData(props.query);return finished.value; //重新更新finished状态};// 上拉加载事件const upRefresh = async () => {page.currentPage += 1;const resData = await getData(props.query);data.value = data.value.concat(resData);return finished.value; //重新更新finished状态};const isRefresh = ref(false);// 获取数据const getData = async (query: object) => {try {const { data, total } = await props.apiService({...page,...query,});totalNum.value = total;isRefresh.value = true;return data || [];} catch (e) {totalNum.value = 0;isRefresh.value = true;return [];}};queryTimer.value = setTimeout(async () => {downRefresh();}, 50);return {data,isRefresh,finished,upRefresh,downRefresh,};
}
</script><style lang="less" scoped>
.scroll {height: 100%;
}
</style>
页面使用
import scrollList from "./scrollList.vue";
....
<scroll-list:apiService="请求方法":query="query"><template #content="{ data }"><!-- 内容们 --></template><scroll-list>
扩展-什么是防抖和节流
相同点
防抖和节流都是为了阻止操作高频触发,从而浪费性能。
区别
// 防抖是触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
//适用于可以多次触发但触发只生效最后一次的场景。
//如王者的回城,在回城中途可能会被频繁打断,但是只有经历完整5s,也就是最后一次能够回城成功。
function debounce(fn, delay){let timer = null;return function(){clearTimeout(timer);timer = setTimeout(()=> {fn.apply(this, arguments);}, delay)}
}
//节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率
//适用于可以在一段时间内触发只生效一次的场景。
//如王者每一个英雄在使用一个技能后,该技能都会有一个固定的冷却时间。冷却时间过后即可再次使用。在技能冷却时间,无论我们连续点击多少次都不会触发技能,而该技能只在单位时间内触发一次。
function throttling(fn, delay) {let timer = null;return (n) => {if (!timer) {timer = setTimeout(() => {fn.call(this, n);clearTimeout(timer)timer = null;}, delay)}}
}