本文详解在Vue.js移动端开发中,如何利用better-scroll库实现上拉加载和下拉刷新功能。文章重点解析了pullDownRefresh与pullUpLoad的配置参数,针对淘宝flexible.js适配导致的屏幕缩放问题提供了动态计算阈值的方法,并封装了可复用的滚动组件,包含事件监听、防抖开关及插槽分发机制,帮助开发者高效构建流畅的列表交互体验。
移动端列表交互功能概述
在移动端应用开发中,上拉加载更多数据与下拉刷新列表是极为常见的交互功能,广泛应用于搜索结果页或各类分类列表页面中。

基于better-scroll的配置与适配处理
与横向滚动效果的实现方式类似,本文继续采用better-scroll库来完成相关功能。鉴于better-scroll已更新至较新版本(如1.2.6),相较于早期的0.x版本,新版提供了更为便捷的API,因此本文基于新API重新编写了实现代码。
首先需完成基础样式的编写,此处省略具体样式代码,随后引入better-scroll库即可开始功能开发。
import BScroll from 'better-scroll'
接着,在Vue组件的mounted生命周期钩子中实例化scroll对象。开发者可以选择在获取完数据后再进行new操作,或者先实例化scroll,待数据获取完成后调用refresh方法更新状态。
实例化过程中需传入配置参数,由于参数选项较多,具体细节建议参考官方文档。此处仅重点讲解两个关键配置项。
//是否开启下拉刷新,可传入true或者false,如果需要更多配置可以传入一个对象
pullDownRefresh:{
threshold:80,
stop:40
}
//是否开启上拉加载,同上,上拉无stop参数,这里需要注意是负数
pullUpLoad:{
threshold:-80,
}
/**
*
* @param threshold 触发事件的阀值,即滑动多少距离触发
* @param stop 下拉刷新后回滚距离顶部的距离(为了给loading留出一点空间)
*/
上述数值经个人测试认为较为合适,但存在一个兼容性问题:由于项目采用淘宝flexible.js进行屏幕适配,导致在不同设备上表现不一致。例如,在安卓设备上80像素的距离触发点较为适宜,但在iPhone 6s等设备上,因屏幕被放大了3倍,实际触发距离仅约为27像素。
因此,针对不同缩放比例的屏幕,必须乘以相应的缩放比来动态调整触发阈值。
淘宝flexible.js源码中已包含获取屏幕缩放比的方法,此处直接复用该逻辑。
//在util.js里面加一个方法
export function getDeviceRatio(){
var isAndroid = window.navigator.appVersion.match(/android/gi);
var isIPhone = window.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = window.devicePixelRatio;
var dpr;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3) {
dpr = 3;
} else if (devicePixelRatio >= 2){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
return dpr
}
import{ DEVICE_RATIO} from '../base/js/api.js'
/*获取当前缩放比*/
const DEVICE_RATIO=getDeviceRatio();
/*下拉配置*/
const DOWN_CONFIG={
threshold:80*DEVICE_RATIO,
stop:40*DEVICE_RATIO
}
/*上拉配置*/
const UP_CONFIG={
threshold:-80*DEVICE_RATIO,
}
this.scroller = new BScroll(scrollWrap,{
click:true,
probeType:3,
pullDownRefresh:DOWN_CONFIG,
pullUpLoad:UP_CONFIG
});
事件监听与流程控制
完成实例化后,下一步是监听上拉和下拉事件。better-scroll新增了一系列事件接口,主要涉及以下两类。
/*下拉事件*/
this.scroller.on('pullingDown',()=> {});
/*上拉事件*/
this.scroller.on('pullingUp',()=>{});
当触发上拉或下拉事件后,必须调用this.scroller.finishPullDown()或this.scroller.finishPullUp()方法来通知better-scroll事件处理完毕,以便恢复滚动状态。
整体业务流程大致如下所示。
this.scroller.on('pullingDown',()=> {
<!-- 1. 发送请求获取数据 -->
<!-- 2. 获取成功后,通知事件完成 -->
<!-- 3. 修改data数据,在nextTick调用refresh -->
});
通常,操作完成后需手动触发refresh方法以重新计算可滚动区域的高度。为此,可以编写一个watch监听器来监控数据变化,这样只需修改数据源,无需在每次操作后手动调用refresh方法。
watch:{
dataList(){
this.$nextTick(()=>{
this.scroller.refresh();
})
}
},
若使用的版本较旧,未包含新增API,则可在scroll事件中通过判断位置来实现相同功能。
this.scroller.on("scroll",(pos)=>{
//获取整个滚动列表的高度
var height=getStyle(scroller,"height");
//获取滚动外层wrap的高度
var pageHeight=getStyle(scrollWrap,"height");
//触发事件需要的阀值
var distance=80*DEVICE_RATIO;
//参数pos为当前位置
if(pos.y>distance){
//console.log("下拉");
//do something
}else if(pos.y-pageHeight<-height-distance){
//console.log("上拉");
//do something
}
为防止事件被多次连续触发,需要引入两个布尔类型的开关变量。
var onPullUp=true;
var onPullDown=true;
每次触发事件时,将对应的开关置为false;待操作完成后,再将其重置为true。这种机制可确保同一时间仅有一个事件在处理,避免重复触发下拉或上拉逻辑。
滚动组件封装与使用
最后,将上述逻辑封装为一个独立的Vue组件。
<template>
<div ref="wrapper" class="list-wrapper">
<div class="scroll-content">
<slot></slot>
</div>
</div>
</template>
由于不同页面需要滚动的具体内容各异,因此使用插槽(slot)来分发内容,以支持多样化的业务场景。
组件所需的配置参数由父组件传入,通过props接收并设置默认值。
export default {
props: {
dataList:{
type: Array,
default: []
},
probeType: {
type: Number,
default: 3
},
click: {
type: Boolean,
default: true
},
pullDownRefresh: {
type: null,
default: false
},
pullUpLoad: {
type: null,
default: false
},
}
组件挂载后,在事件触发时并不直接处理业务逻辑,而是向父组件发射自定义事件。父组件通过模板中的v-on指令接收这些事件,并负责处理后续的具体逻辑。
mounted() {
this.scroll = new BScroll(this.$refs.wrapper, {
probeType: this.probeType,
click: this.click,
pullDownRefresh: this.pullDownRefresh,
pullUpLoad: this.pullUpLoad,
})
this.scroll.on('pullingUp',()=> {
if(this.continuePullUp){
this.beforePullUp();
this.$emit("onPullUp","当前状态:上拉加载");
}
});
this.scroll.on('pullingDown',()=> {
this.beforePullDown();
this.$emit("onPullDown","当前状态:下拉加载更多");
});
}
父组件在使用该组件时,需传入配置参数Props,监听子组件发射的事件,并使用具体内容替换slot标签。
<Scroller
id="scroll"
ref="scroll"
:dataList="filmList"
:pullDownRefresh="DOWN_CONFIG"
:pullUpLoad="UP_CONFIG"
@onPullUp="pullUpHandle"
@onPullDown="pullDownHandle"
>
<ul>
<router-link class="film-list" v-for="(v,i) in filmList" :key="v.id" tag="li" :to='{path:"/film-detail/"+v.id}'>
<div class="film-list__img">
<img v-lazy="v.images.small" alt="" />
</div>
<div class="film-list__detail">
<p class="film-list__detail__title">{{v.title}}</p>
<p class="film-list__detail__director">导演:{{filterDirectors(v.directors)}}</p>
<p class="film-list__detail__year">年份:{{v.year}}<span>{{v.stock}}</span></p>
<p class="film-list__detail__type">类别:{{v.genres.join(" / ")}}<span></span></p>
<p class="film-list__detail__rank">评分:<span>{{v.rating.average}}分</span></p>
</div>
</router-link>
</ul>
</Scroller>
父组件可通过this.$refs.xxx获取子组件实例,进而调用子组件内部暴露的方法。
computed:{
scrollElement(){
return this.$refs.scroll
}
}
完整的scroller组件代码如下所示。
<template>
<div ref="wrapper" class="list-wrapper">
<div class="scroll-content">
<slot></slot>
<div>
<PullingWord v-show="!inPullUp&&dataList.length>0" :loadingWord="beforePullUpWord"></PullingWord>
<Loading v-show="inPullUp" :loadingWord='PullingUpWord'></Loading>
</div>
</div>
<transition name="pullDown">
<Loading class="pullDown" v-show="inPullDown" :loadingWord='PullingDownWord'></Loading>
</transition>
</div>
</template>
<script >
import BScroll from 'better-scroll'
import Loading from './loading.vue'
import PullingWord from './pulling-word'
const PullingUpWord="正在拼命加载中...";
const beforePullUpWord="上拉加载更多";
const finishPullUpWord="加载完成";
const PullingDownWord="加载中...";
export default {
props: {
dataList:{
type: Array,
default: []
},
probeType: {
type: Number,
default: 3
},
click: {
type: Boolean,
default: true
},
pullDownRefresh: {
type: null,
default: false
},
pullUpLoad: {
type: null,
default: false
},
},
data() {
return {
scroll:null,
inPullUp:false,
inPullDown:false,
beforePullUpWord,
PullingUpWord,
PullingDownWord,
continuePullUp:true
}
},
mounted() {
setTimeout(()=>{
this.initScroll();
this.scroll.on('pullingUp',()=> {
if(this.continuePullUp){
this.beforePullUp();
this.$emit("onPullUp","当前状态:上拉加载");
}
});
this.scroll.on('pullingDown',()=> {
this.beforePullDown();
this.$emit("onPullDown","当前状态:下拉加载更多");
});
},20)
},
methods: {
initScroll() {
if (!this.$refs.wrapper) {
return
}
this.scroll = new BScroll(this.$refs.wrapper, {
probeType: this.probeType,
click: this.click,
pullDownRefresh: this.pullDownRefresh,
pullUpLoad: this.pullUpLoad,
})
},
beforePullUp(){
this.PullingUpWord=PullingUpWord;
this.inPullUp=true;
},
beforePullDown(){
this.disable();
this.inPullDown=true;
},
finish(type){
this["finish"+type]();
this.enable();
this["in"+type]=false;
},
disable() {
this.scroll && this.scroll.disable()
},
enable() {
this.scroll && this.scroll.enable()
},
refresh() {
this.scroll && this.scroll.refresh()
},
finishPullDown(){
this.scroll&&this.scroll.finishPullDown()
},
finishPullUp(){
this.scroll&&this.scroll.finishPullUp()
},
},
watch: {
dataList() {
this.$nextTick(()=>{
this.refresh();
})
}
},
components: {
Loading,
PullingWord
}
}
</script>
具体实现细节可访问GitHub查看,项目地址为:https://github.com/linrunzheng/vueApp。
以上就是本文的全部内容,希望能为各位的学习提供帮助,也欢迎大家继续支持本站。