@vue/cli 5.0.6 vue@2.7.0
为了方便在其它项目中使用此功能,我们将功能封装到一个组件中,使用时只需引入此组件即可,避免重复性工作
1:html结构布局
<template>
<div class="wrapper" ref="wrapper" v-on:scroll="infiniteScroll">
<div class="content">
<slot></slot>
</div>
</div>
</template>
-
必须有两个div,外层负责滚动(class="wrapper"),内层负责存放内容(class="content")
-
外层div(class="wrapper")添加ref属性,会在接下来的逻辑部分用到
-
外层div(class="wrapper")添加v-on:scroll="infiniteScroll",这是核心逻辑
-
内层div(class="content")放一个slot插槽标签,调用组件时直接在组件标签内放入需要滚动的内容,可直接替换
-
外层盒子必须有高度、必须有overflow属性,不然无法实现滚动(元素高度根据实际情况设置)
.wrapper { width: 100%; margin: 0 auto; height: calc(100vh - 95px);//calc计算:设备高度减去95个像素得到的值就是wrapper的高度 overflow: auto; .content { width: 95%; margin: 0 auto; } }
2:逻辑部分
1:在data中准备需要用到的状态
data() {
return {
scrollTop: 0,
scrollHeight: 0,
wrapperHeight: 0,
triggerDistance: 1,
isTrigger: false
}
}
-
scrollTop:表示已经滚动的距离
-
scrollHeight:表示一共可以滚动的距离
-
wrapperHeight:表示元素wrapper的高度
-
triggerDistance:表示触发上拉加载的距离(触发距离也可以通过props传递)
-
isTrigger:防止在ios端频繁调用infiniteScroll方法
2:在mounted中获取wrapper的高度
mounted() {
/*组件挂载时获取wrapper的高度*/
this.$nextTick(() => {
this.wrapperHeight = this.$refs.wrapper.offsetHeight
})
}
3:在methods中实现各种方法
1:获取可滚动距离
obtainScrollHeight() {
let _scrollHeight = this.$refs.wrapper.scrollHeight
if (_scrollHeight > this.scrollHeight) {
this.scrollHeight = _scrollHeight
}
}
-
为什么要在这里加一个判断?
-
原因1:避免滚动事件发生时频繁的向scrollHeight写入数据,大多数时候scrollHeight的值是相同的,当我们发送了网络请求,并且数据渲染到页面后,此时的_scrollHeight的值才大于scrollHeight
-
原因2:避免导致页面可滚动距离值小于异步任务完成后页面实际可滚动距离,有时会因为服务器或自身的网络原因导致数据加载缓慢,尤其是图片资源,图片资源没到加载时div是撑不开的
-
其它方法:也可以给图片添加v-on:load事件,图片加载完成成时会触发此事件,然后通过全局事件总线调用方法获取可滚动距离
-
2:获取已滚动距离
obtainScrollTop() {
this.scrollTop = this.$refs.wrapper.scrollTop
}
3:滚动到底部时触发的方法
triggerEvent() {
this.$emit('loadGoods')
}
-
可以在这里发送网络请求,也可以通过this.$emit发出一个事件,在组件调用时通过v-on:loadGoods="loadGoods"监听事件
<InfiniteScroll ref="wrapper" v-on:loadGoods="loadGoods"> <div> <!-- 需要滚动的内容 --> </div> </InfiniteScroll>
loadGoods() { console.log('发送网络请求') }
4:滚动发生时触发的方法
infiniteScroll() {
this.obtainScrollHeight() /*滚动发生时调用obtainScrollHeight方法,获取页面可以滚动区域*/
this.obtainScrollTop() /*滚动发生时调用obtainScrollTop方法,获取页面已经滚动的距离*/
this.$emit('judgeDistance') /*滚动发生时,触发事件judeDistance,判断是否显示返回顶部按钮*/
if (this.scrollTop + this.wrapperHeight > this.scrollHeight - this.triggerDistance) {
/*判断:如果scrollTop加上wrapperHeight的值大于了scrollHeight减去triggerDistance的值*/
/*调用方法triggerEvent 调用了triggerEvent就相当于发送了网络请求*/
if (!this.isTrigger) {
this.triggerEvent()
this.isTrigger = true
setTimeout(() => {
this.isTrigger = false
}, 500)
}
}
}
-
滚动发生时调用obtainScrollHeight方法,获取页面可以滚动区域
-
滚动发生时调用obtainScrollTop方法,获取页面已经滚动的距离
-
判断:如果scrollTop加上wrapperHeight的值大于了scrollHeight减去triggerDistance的值,则发送网络请求
-
再次判断isTrigger的值,主要是防止在ios端上拉到底部时会回弹,导致多次触发网络请求
-
发送网络请求或发送事件给父组件
-
添加一个定时器将isTrigger的值修改回false,便于下次发送请求
3:注意点
scrollHeight的值容易受到异步任务中的图片影响,所以我们需要通过各种方法在异步任务完成后并且图片加载到页面时获取scrollHeight的值,不然可滚动距离和触发距离都会出错。可以通过滚动事件(scroll)来获取,也可以通过给图片添加v-on:load事件来获取,如果图片资源过多,可以添加一个节流函数,避免多次频繁调用
4:代码
1:html
<template>
<div class="wrapper" ref="wrapper" v-on:scroll="infiniteScroll">
<div class="content" ref="content">
<slot></slot>
</div>
</div>
</template>
2:javaScript
export default {
name: "InfiniteScroll",
data() {
return {
scrollTop: 0, //已经滚动的距离
scrollHeight: 0, //可滚动距离
wrapperHeight: 0,//wrapper元素的高度
isTrigger: false //防止在ios端频繁调用infiniteScroll方法
}
},
props: {
triggerDistance: {//触发距离
type: Number,
default: 1
},
},
methods: {
infiniteScroll() {
this.obtainScrollHeight()/*滚动发生时调用obtainScrollHeight方法,获取页面可以滚动区域*/
this.obtainScrollTop() /*滚动发生时调用obtainScrollTop方法,获取页面已经滚动的距离*/
this.$emit('judgeDistance')/*滚动发生时,触发事件judeDistance,判断是否显示返回顶部按钮*/
if (this.scrollTop + this.wrapperHeight > this.scrollHeight - this.triggerDistance) {
/*判断:如果scrollTop加上wrapperHeight的值大于了scrollHeight减去triggerDistance的值*/
/*调用方法triggerEvent*/
if (!this.isTrigger) {
this.triggerEvent()
this.isTrigger = true
setTimeout(() => {
this.isTrigger = false
}, 500)
}
}
},
triggerEvent() {//无限滚动最终触发的方法,这里可以放网络请求代码,也可以向父组件发送事件
this.$emit('loadGoods')/*滚动到底部时触发事件loadGoods,发送网络请求*/
},
obtainScrollTop() { //获取已经滚动的距离
this.scrollTop = this.$refs.wrapper.scrollTop
},
obtainScrollHeight() {//获取可滚动距离
let _scrollHeight = this.$refs.wrapper.scrollHeight
if (_scrollHeight > this.scrollHeight) {
/*为什么要在这里加一个判断?
原因1:避免滚动事件发生时频繁的向scrollHeight写入数据,大多数时候_scrollHeight和scrollHeight的值是相同的,
当我们发送了网络请求,并且数据渲染到页面后,此时的_scrollHeight的值才大于scrollHeight
原因2:避免导致页面滚动距离的值小于异步任务完成后页面实际可滚动距离,有时候会因为服务器或自身的网络原因导致数据加载缓慢(尤其是图片)*/
this.scrollHeight = _scrollHeight
}
}
},
mounted() {
/*组件挂载时获取wrapper的高度*/
this.$nextTick(() => {
this.wrapperHeight = this.$refs.wrapper.offsetHeight
})
}
}
3:less(css)
/*最外层的wrapper盒子可以在此组件内设置height和overflow属性,也可以在调用此组件的父级设置,根据实际情况灵活使用*/
.wrapper {
width: 100%;
margin: 0 auto;
height: calc(100vh - 95px); /*wrapper必须设置高度,不然无法实现滚动*/
overflow: auto; /*必须设置overflow属性,不然无法实现滚动*/
.content {
width: 95%;
margin: 0 auto;
}
}
QQ录屏20220905100932