vue項目中自己實現下拉刷新和上拉加載
1、頁面監聽版(簡單)
在mounted里監聽頁面滾動
其實是只要監聽滾動盒子的scroll事件即可,但有的時候滾動的是整個頁面,那我們就直接監聽window的滾動就可以:
window.addEventListener('scroll', this.onContentScroll)
監聽頁面滾動事件
-
獲取當前滾動的高度scrollTop(代碼里這種寫法好好理解並記憶,囊括了各種情況);
-
獲取當前可視高度clientHeight;
-
獲取當前整個頁面(包括可滾動區域,其實就是滾動的整個盒子)的高度scrollHeight;
-
當
scrollTop + clientHeight >= scrollHeight
時即可證明滑到底部了;- 這里減二是為了消除像素在瀏覽器里顯示時候出現的小小誤差;
-
滑動到底部看當前是否有更多數據(hasMore)可獲得以及當前是否正在獲取數據(busy)
onContentScroll (e) {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
let clientHeight = document.documentElement.clientHeight;
let scrollHeight = document.documentElement.scrollHeight;
if (scrollTop + clientHeight >= scrollHeight-2) {
if (this.hasMore && !this.busy) {
this.loadMore()
}
}
},
加載更多(loadMore)函數
- 設置當前處於正在加載狀態(busy置為true),其實相當於加了把鎖(最近學os學傻了...;
- 獲取更多數據,且在回調里將加載狀態消除。
loadMore () {
this.busy = true
this.page++
this.getJobList()
.then(() => {
this.busy = false
})
}
2、另寫組件版(略復雜)
就不仔仔細細講了,原理和上面的簡單版差不多,只是擴充了一些內容以及加上手勢操作。
-
監聽頁面的touchstart和touchend事件
-
在touchstart中記錄下手指開始滑動的位置;
-
在touchend中記錄下手指結束滑動的位置;
-
將開始和結束位置進行比較:
- 先判斷滑動方向(上拉刷新,下拉加載),這一步還挺復雜 主要是判斷手指的各種滑動情況比較復雜;
- 若是上拉就看是否滑到底部,這一步的話主要是計算各種盒子的高度比較麻煩思路其實不難,難在布局;
- 若是下拉就看當前距離頂部的高度(其實就是上面簡單版里的scrollTop),若為0了說明回到原位,開始刷新。
附源代碼:
<template>
<div class="loadmoreBox flex flexh flexvc" @touchstart="touchStart($event)" @touchend="touchEnd($event)">
<div class="refreshBox flex flexc" v-if="refresh">
<van-loading class="refreshLoading" type="spinner" />
</div>
<slot></slot>
<div class="loadmore" v-if="status==1">上拉加載更多...</div>
<van-loading class="loadmore" v-else-if="status==2" type="spinner" />
<div class="loadmore" v-else-if="status==3">我是有底線的</div>
</div>
</template>
<script>
import { Loading as VanLoading } from 'vant';
export default {
name: 'LoadMore',
props:{
refresh:{
type:Boolean,
default:false
},
status:{
type:Number,
default:0
}
},
data(){
return {
startX:0,
startY:0,
}
},
components:{
VanLoading
},
methods:{
touchStart(e){
this.startY = e.targetTouches[0].pageY;
this.startX = e.targetTouches[0].pageX;
},
touchEnd(e){
let endY = e.changedTouches[0].pageY;
let endX = e.changedTouches[0].pageX;
let direct=this.getSlideDirection(this.startX,this.startY,endX,endY);
console.log(direct);
if(direct==1){ //上拉加載
let isEnd=this.scrollToTheEnd();
if(isEnd&&this.status==1){
this.$emit('loadMore');
}
}else if(direct==2){ //下拉刷新
// 獲取滾動條距離頂部的距離
let scrollToTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;
if(!scrollToTop){
this.$emit('onRefresh');
}
}
},
/**
* 判斷滾動條是否到底
*/
scrollToTheEnd () {
//獲取窗口的高度
let clientHeight = document.documentElement.clientHeight;
//獲取滾動元素距離文檔 頂部的距離
let elementTop=document.querySelector('.loadmoreBox').offsetTop;
//獲取滾動元素高度
let elementHeight=document.querySelector('.loadmoreBox').offsetHeight;
//獲取頂部固定區域高度
let topFixEl=document.querySelector('.topFixBlank');
let topFixHeight=topFixEl?topFixEl.offsetHeight:0;
//獲取加載板塊高度
let loadmoreEl=document.querySelector('.loadmore');
let loadmoreH=loadmoreEl?loadmoreEl.offsetHeight:0;
// 獲取滾動條距離頂部的距離
let scrollToTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset
// 獲取滾動條的總高度
let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
//整個列表的高度
let pageHeight=elementTop+elementHeight-topFixHeight-loadmoreH;
//判斷是否到底
if(pageHeight+Math.ceil(scrollToTop)>scrollHeight){ //如果列表高度+滾動距離大於滾動條高度,則代表到底了
return true;
}else{
return false;
}
},
/**
* 返回角度
*/
getSlideAngle (dx, dy) {
return Math.atan2(dy, dx) * 180 / Math.PI
},
/**
* 根據起點和終點返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑動
* @param {number} startX X軸開始位置
* @param {number} startY X軸結束位置
* @param {number} endX Y軸開始位置
* @param {number} endY Y軸結束位置
*/
getSlideDirection (startX, startY, endX, endY) {
let dy = startY - endY
let dx = endX - startX
let result = 0
// 如果滑動距離太短
if (Math.abs(dx) < 2 && Math.abs(dy) < 2) {
return result
}
let angle = this.getSlideAngle(dx, dy)
if (angle >= -30 && angle < 30) {
result = 4
} else if (angle >= 30 && angle < 150) {
result = 1
} else if (angle >= -150 && angle < -30) {
result = 2
} else if ((angle >= 150 && angle <= 180) || (angle >= -180 && angle < -150)) {
result = 3
}
return result
}
}
}
</script>
<style lang="scss" scoped="scoped">
.refreshBox{
width: 100%;
height: auto;
overflow: hidden;
margin-bottom: 10px;
}
.refreshLoading{
width: 20px;
height:20px;
}
.loadmoreBox{
width: 100%;
height:auto;
}
.loadmore{
width: 100%;
height: 30px;
text-align: center;
line-height: 30px;
font-size: 14px;
color: #666666;
}
</style>