当前位置:网站首页>Write a drop-down refresh component
Write a drop-down refresh component
2022-08-10 03:21:00 【small white eyes】
在移动端开发中,不管是iOS还是android ,The drop-down list is loaded frequently,So the necessity of realizing this function is self-evident,There are also many libraries on the side that can do this.In general, pull-down refresh or pull-up loading is achieved by adding sliding event listeners,Judging page swipe distance and gestures to determine when to load data and give feedback,Apart from that foriOS和androidenvironmental differences,Fault tolerance may be required where necessary.
The core logic is to rely ontouchstart touchmove touchend 事件监听做处理,
- Touch to start will execute touchstart事件,记录当前的Y坐标 startY;
- Listen as you move your fingertouchMove事件:Finger movement records the current portrait in real timeY坐标 pageY , 而pageY - startYis the distance moveddistance;此时最新的topvalue equal to starttopPlus the mouse movement distance: element.style.top = startTop + distance ;
- Release your finger to trigger touchend事件,Just if the movement exceeds a certain thresholdthreshold就会执行回调(Event to refresh data)实现回弹效果.
下面用vue实现:
template:
<div :class="{ container: refreshEnable }" :style="containerStyle">
<div v-if="refreshEnable && [1, 2, 3, 4].indexOf(refreshStatus) >= 0">
<div
class="top-tip"
v-if="refreshStatus === 1"
:class="{ transition: !noAnimation }"
:style="{ transform: 'translateY(' + (refreshing ? refreshingY : pullY) + 'px)' }"
>
<span class="refresh-icon" v-if="pullY > threshold"><img src="@assets/img/up.png" /></span>
<span class="refresh-icon" v-else><img src="@assets/img/down.png" /></span>
<span class="refresh-msg">{
{ pullY > threshold ? '松开' : '下拉' }}刷新</span>
</div>
<!-- Refresh the prompt information:状态2:开始加载数据,未完成状态 -->
<div class="top-loading" v-else-if="refreshStatus === 2">
正在加载…
</div>
<!-- Refresh the prompt information:状态3:The data has been loaded and displayed successfully -->
<div class="top-success" v-else-if="refreshStatus === 3">
The latest content has been discovered for you
</div>
<!-- Refresh the prompt information:状态4:The data has been loaded but nothing new -->
<div class="top-norefresh" v-else-if="refreshStatus === 4">
There is no new news ahead~
</div>
</div>
<div
class="wrapper"
ref="wrapper"
:class="{ transition: !noAnimation }"
:style="wrapperY ? { transform: 'translateY(' + wrapperY + 'px)' } : {}"
>
</div>
<!-- Always keep space at the bottom to prevent flickering -->
<div class="common-bottom" v-if="loadMoreEnable && [1, 2].indexOf(loadingStatus) >= 0">
<!-- Loading tips at the bottom:状态1: 正在加载数据 -->
<div class="bottom-loading" v-show="loadingStatus === 1">
正在加载…
</div>
<!-- Loading tips at the bottom:状态2: Prompt that the last piece of data has been loaded -->
<div class="bottom-limit" v-show="loadingStatus === 2">
You actually read it all Let's find another location ~
</div>
</div>
</div>
script:
import { throttle } from '@util/throttle'; //节流函数
import scroll from '@mixins/scroll'; // 监听滚动事件
data:
data() {
return {
refreshStatus: 0,
loadingStatus: 0,
pullY: 0,
refreshing: false,
loading: false,
noAnimation: false,
threshold: 70,
refreshingY: 0,
};
},
props:
props: {
// 是否允许下拉刷新
refreshEnable: {
type: Boolean,
default: true,
},
// Sliding callback function
onRefresh: {
type: Function,
default() {},
},
// The callback function for sliding up
onLoad: {
type: Function,
default() {},
},
loadMoreEnable: {
type: Boolean,
default: true,
},
hasMore: {
type: Boolean,
default: true,
},
mpsTipHeight: {
type: Number,
default: 0,
},
navigatorHeight: {
type: Number,
default: 0,
},
mpsTipShow: {
type: Boolean,
default: false,
},
},
watch:
watch: {
hasMore: {
handler(newValue) {
if (this.loadMoreEnable) {
this.loadingStatus = newValue ? 0 : 2;
}
},
immediate: true,
},
},
computed:
computed: {
containerStyle() {
let marginTop = 0;
if (this.refreshEnable) {
if (this.mpsTipShow) {
let baseMarginTop = 29;
marginTop = `${this.mpsTipHeight + baseMarginTop}px`;
} else {
// 随tips高度变化
marginTop = `${(this.navigatorHeight || 86) - 5}px`;
}
}
return {
marginTop,
};
},
wrapperY() {
return this.refreshing ? this.refreshingY : this.pullY;
},
},
methods:
methods: {
touchEndHandlers(type) {
if (!this.refreshEnable) {
return;
}
// debugger;
let vm = this;
if (vm.refreshing) return;
vm.refreshStatus = 2;
vm.refreshing = true;
let r = vm.onRefresh(type);
if (r && r.then) {
r.then((res) => {
vm.refreshing = false;
vm.pullY = (54 / 375) * getClientWidth();
if (res && res === 4) {
vm.refreshStatus = 4;
} else if (res && res === 3) {
vm.refreshStatus = 3;
}
setTimeout(() => {
vm.refreshStatus = 0;
vm.pullY = 0;
}, 500);
}).catch((error) => {
vm.refreshStatus = 0;
vm.refreshing = false;
vm.pullY = 0;
console.error(error);
});
}
},
// Swipe-triggered function:throttling
scrollHandlers() {
let vm = this;
let scrollTop = this.getScrollTop();
let bodyHeight = document.documentElement.clientHeight || document.body.clientHeight;
let bodyWidth = document.documentElement.clientWidth || document.body.clientWidth;
if (!vm.$refs.wrapper || !this.hasMore || !this.loadMoreEnable) return;
let containerHeight = vm.$refs.wrapper.clientHeight;
// If in the end load data,距离底端150pxstart loading data
if ((scrollTop + bodyHeight) > (containerHeight - ((200 / 375) * bodyWidth)) && !vm.loading) {
vm.loading = true;
// onLoad为一个返回promise对象的函数
vm.loadingStatus = 1;
let l = vm.onLoad();
if (l && l.then) {
l.then((res) => {
if (res === -2) {
vm.loadingStatus = 2;
vm.loading = false;
} else if (res === -1) {
vm.loadingStatus = 0;
vm.loading = false;
} else {
vm.loading = false;
}
}).catch((error) => {
vm.loadingStatus = 0;
vm.loading = false;
console.error(error);
});
}
}
},
forceRefresh(type) {
window.scrollTo(0, 0);
this.touchEndHandlers(type);
},
},
mounted:
mounted() {
let vm = this;
// Add sliding event listener,Used to determine when to load data,节流
window.addEventListener('scroll', throttle(vm.scrollHandlers, 20), true);
if (this.refreshEnable) {
// 添加滑动事件,Used for judgment monitoringscrollTop:1. 判断滑动方向 2.Determine if it's nearing the end
let start = false;
let startY = null;
let lastMove = null;
// After pulling down and rebounding,刷新过程中,即refreshing为true时,The list bounces back to a certain height
vm.refreshingY = (54 / 375) * getClientWidth();
// If only a small area is swiped,Then the place where the touch starts needs to be within this area
this.$refs.wrapper.addEventListener('touchstart', () => {
start = true;
});
window.addEventListener('touchstart', (ev) => {
lastMove = ev.changedTouches[0].pageY;
});
window.addEventListener(
'touchmove',
(ev) => {
let scrollTop = this.getScrollTop();
if (start && scrollTop <= 0 && lastMove !== null) {
let now = ev.changedTouches[0].pageY;
let diff = now - lastMove;
// Determine the direction of the instantaneous slide
if (diff > 0) {
if (ev.cancelable) {
ev.preventDefault();
}
// Block the browser by defaultscroll事件
vm.refreshStatus = 1;
} else {
// Swipe up momentarily,Scroll down overall
if (vm.pullY > 0) {
if (ev.cancelable) {
ev.preventDefault();
}
vm.refreshStatus = 1;
}
}
if (!startY) {
startY = now;
}
let targetHeight = now - startY;
vm.pullY = Math.max(targetHeight, 0) / 2;
}
// Stop the animation when the finger is pulled down
vm.noAnimation = true;
lastMove = ev.changedTouches[0].pageY;
},
{ passive: false }
);
window.addEventListener('touchend', () => {
if (vm.refreshing) return;
if (vm.pullY > vm.threshold) {
vm.touchEndHandlers();
} else {
vm.pullY = 0;
vm.refreshStatus = 0;
}
startY = null;
lastMove = null;
start = false;
vm.noAnimation = false;
});
}
},
mixins:
/**
* 页面滚动
* @module mixins/scroll
*/
export default {
methods: {
/**
* 是否为iOS
* @ignore
* @return {Boolean} 是否为iOS
*/
isIOS() {
const UA = window.navigator.userAgent.toLowerCase();
return UA && /iphone|ipad|ipod|ios/.test(UA);
},
/**
* Add a continuous scroll event listener for the page
* 由于ios的UIWebviewThere is scrollingbug,So do compatibility processing
* @ignore
* @param {Function} callback The callback function when scrolling occurs
*/
addBodyScrollListener(callback) {
document.addEventListener('scroll', callback);
if (this.isIOS && typeof window.ontouchmove !== 'undefined') {
document.addEventListener('touchmove', callback);
}
},
/**
* 获取页面的scrollTop
* @ignore
* @return {Number} 页面的scrollTop
*/
getScrollTop() {
return document.body.scrollTop || document.documentElement.scrollTop;
},
/**
* Scrolls the page to the specified position
* @ignore
* @param {Number} top The abscissa of the target position
*/
scrollTop(top) {
document.body.scrollTop = top;
document.documentElement.scrollTop = top;
},
/**
* rem转换为px
* @ignore
* @param {String} rem 待转换的rem值
* @return {Number} 转换后的px值
*/
rem2px(rem) {
const { fontSize } = document.getElementsByTagName('html')[0].style;
return `${parseFloat(rem) * parseFloat(fontSize)}px`;
},
/**
* px转换为rem
* @ignore
* @param {String} px 待转换的px值
* @return {Number} 转换后的rem值
*/
px2rem(px) {
const { fontSize } = document.getElementsByTagName('html')[0].style;
return `${parseFloat(px) / parseFloat(fontSize)}rem`;
},
/**
* 无论是rem还是px单位,are converted to integerspx数值
* @ignore
* @param {String} value 输入值
* @return {Number} 转换后的px数值
*/
remOrPxToPxNumber(value) {
if (value.toString().indexOf('rem') > -1) {
// rem转px
return parseFloat(this.rem2px(value));
}
return parseFloat(value);
},
},
};
边栏推荐
猜你喜欢
随机推荐
LeetCode每日两题01:移动零 (均1200道)方法:双指针
【引用计数器及学习MRC的理由 Objective-C语言】
QT模态对话框及非模态对话框学习
ImportError: Unable to import required dependencies: numpy
Maya制作赛博朋克机器人模型
官宣出自己的博客啦
【每日一题】1413. 逐步求和得到正数的最小值
On the Harvest of Travel
web crawler error
[网鼎杯 2020 青龙组]AreUSerialz
SQL注入的order by ,limit与宽字节注入
Nacos源码分析专题(五)-Nacos小结
【8.8】代码源 - 【不降子数组游戏】【最长上升子序列计数(Bonus)】【子串(数据加强版)】
2022.8.8 Exam Travel Summary
Pagoda server PHP+mysql web page URL jump problem
数据库治理利器:动态读写分离
UXDB现在支持函数索引吗?
中级xss绕过【xss Game】
2022年8月8日-2022年8月15日,ue4视频教程+插件源码()
微生物是如何影响身体健康的