移动端-vue-BScroll用法教程
简介
BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件,是最接近与原生的滚动插件,兼容pc和移动端。
官网
https://better-scroll.github.io/docs/zh-CN/
安装
javascript
npm install better-scroll --save
使用
javascript
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
});
移动端滚动使用
javascript
<template>
<div
class="scroll"
ref="scrollRef"
>
<div class="scroll-wrap">
<div
class="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>
<div
class="pullup"
v-if="pullUpRefresh"
>
<div
v-if="!isPullUpLoad && !loading"
class="before-trigger"
>
<span class="pullup-txt">{{ finished ? "暂无更多数据" : "上拉加载更多数据" }}</span>
</div>
<div
v-else
class="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>
移动端联合滚动实现懒加载
javascript
<!--
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"
>
<template
v-for="(item, index) in data"
:key="index"
>
<slot
name="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函数的上下文 用于获取props
const 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>
页面使用
javascript
import scrollList from "./scrollList.vue";
....
<scroll-list
:apiService="请求方法"
:query="query"
>
<template #content="{ data }">
<!-- 内容们 -->
</template>
<scroll-list>
扩展-什么是防抖和节流
相同点
防抖和节流都是为了阻止操作高频触发,从而浪费性能。
区别
javascript
// 防抖是触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
//适用于可以多次触发但触发只生效最后一次的场景。
//如王者的回城,在回城中途可能会被频繁打断,但是只有经历完整5s,也就是最后一次能够回城成功。
function debounce(fn, delay){
let timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(()=> {
fn.apply(this, arguments);
}, delay)
}
}
javascript
//节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率
//适用于可以在一段时间内触发只生效一次的场景。
//如王者每一个英雄在使用一个技能后,该技能都会有一个固定的冷却时间。冷却时间过后即可再次使用。在技能冷却时间,无论我们连续点击多少次都不会触发技能,而该技能只在单位时间内触发一次。
function throttling(fn, delay) {
let timer = null;
return (n) => {
if (!timer) {
timer = setTimeout(() => {
fn.call(this, n);
clearTimeout(timer)
timer = null;
}, delay)
}
}
}