先看一下效果图

下拉效果的样子参考的新浪微博,滚动加载是ydui的滚动加载组件

因为滚动加载使用的ydui的组件,我这里便不再累述

在线体验点这里

首先分析下拉刷新是怎么实现的

1.页面滚动到顶部时,用户手指向下拖动
2.页面整体开始随着手指向下移动,同时出现下拉的动画
3.用户拖动超过指定长度之后松开手指,页面开始回弹并且执行加载中的动画
4.加载完成之后执行结束的动画

实现原理

一、touchstart事件中1.判断是不是滚动到了顶部,如果不是则什么也不用做2.判断上次的下拉刷新是不是结束了,如果没有则阻止浏览器默认行为3.如果滚动到了顶部 且 没有进行中的下拉刷新 则记录触摸的位置event.touches[0].clientY
二、touchmove事件中1.判断是不是滚动到了顶部,如果不是则什么也不用做2.判断上次的下拉刷新是不是结束了,如果没有则阻止浏览器默认行为3.如果滚动到了顶部 且 没有进行中的下拉刷新1.判断手指是向上滑还是向下滑,向上则正常滚动页面,向下则执行下拉刷新2.如果手指向下拉,则判断滑动的距离是否超过了指定的距离,超过了则改变动画效果(由下拉刷新-> 释放刷新)
三、touchend事件中与touchmove中判断同,唯一不同的是滑动的距离是否超过了指定的距离触发回调并且执行刷新中的动画

获取页面滚动的位置(或者是div内部滚动位置)

/*** 传入一个dom对象,返回获取滚动的位置* @method getScrollTop* @param {dom} dom节点* @return {Number} 滚动的位置*/function getScrollTop(element) {if (element === window) {return Math.max(window.pageYOffset || 0,document.documentElement.scrollTop)} else {return element.scrollTop}}

事件处理

//touchstart事件
function touchStartHandler(event) {//正在执行下拉刷新则返回if (this.touches.loading) {event.preventDefault()return}//当向下滚动了则直接返回//this.getScrollTop(this.scrollview)获取元素滚动的位置,实现方法见源码//this.$refs.dragBox.getBoundingClientRect().top为元素距离窗口顶部的距离//this.offsetTop为页面初始化时 this.$refs.dragBox.getBoundingClientRect().top的值if (this.getScrollTop(this.scrollview) > 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {return}//数据初始化this.touches.loading = false //是否在下拉刷新回调中this.touches.startClientY = 0 //触摸初始位置this.touches.isDraging = false //是否开始下拉刷新this.touches.statusText = '下拉刷新' //下拉刷新动画中的描述文字this.moveOffset = 0 //手指滑动的距离//记录触摸位置this.touches.startClientY = event.touches[0].clientY
}//touchmove事件
function touchMoveHandler(event) {const touches = this.touches//记录当前触摸位置,为了和下一个触摸位置作比较,判断是向上还是向下移动touches.currentClientY = event.touches[0].clientY//当向下滚动了则直接返回if (this.getScrollTop(this.scrollview) > 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {this.touches.isDraging = false //没有开始下拉刷新this.moveOffset = 0 //手指滑动的距离0return}//正在执行下拉刷新则返回if (this.touches.loading) {event.preventDefault()return}//当前触摸的位置const currentY = event.touches[0].clientY//防止手指直接下滑造成页面不能正常的滚动if (!touches.isDraging && currentY < touches.startClientY) {return}//手指先先下拉,再向上滑,说明此时手指已经在触摸位置上方了if (touches.isDraging &&(currentY - touches.startClientY < 0 ||this.$refs.dragBox.getBoundingClientRect().top <this.offsetTop)) {// this.isDragToUp = true;event.preventDefault()return}//手指向下滑if (touches.isDraging && this.getScrollTop(this.scrollview) === 0) {event.preventDefault()}// //开始下拉刷新this.touches.isDraging = true//手指滑动的距离let deltaSlide = currentY - touches.startClientY//如果超过了指定的距离, 达到了释放更新的条件//touches.distance为顶部加载中动画的高度//this.double为手指移动距离和页面实际移动距离的倍数if (deltaSlide >= touches.distance * this.double) {this.touches.statusText = '释放更新'} else {this.touches.statusText = '下拉刷新'}//记录滑动的距离this.moveOffset = deltaSlide
}//touchend事件
function touchEndHandler(event) {const touches = this.touches//正在执行下拉刷新则返回if (this.touches.loading) {event.preventDefault()return}//当向下滚动了则直接返回if (this.getScrollTop(this.scrollview) > 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {this.touches.isDraging = falsethis.moveOffset = 0return}const currentY = event.changedTouches[0].clientY//说明此时手指已经在触摸位置上方了if (currentY - touches.startClientY < 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {this.touches.isDraging = falseevent.preventDefault()return}//下拉刷新阻止浏览器默认行为if (this.getScrollTop(this.scrollview) === 0) {event.preventDefault()}//手指滑动的距离let deltaSlide = currentY - touches.startClientY//如果超过了指定的距离if (deltaSlide >= touches.distance * this.double) {//进行更新的回调及动画的改变,这部分见源码,耐心的看源码,还是能很容易看明白的some code...} else {this.touches.isDraging = false//距离不够则不刷新this.Retract(0, false)}
}

上方只展示了主要部分,完整源码见文末

然后我把下拉刷新和上拉加载封装为一个vue组件
使用方法

//引入
import YdInfinitescroll from './components/InfiniteScroll.vue'
//注册
components: { YdInfinitescroll }
//使用
<yd-infinitescroll:pullcallback="pullcallback":callback="callback"ref="infinitescrollDemo"
><div slot='list'>...这里放你的内容</div>
</yd-infinitescroll>

说明:

1.传入callback参数表示开启滚动加载功能1.this.$refs.infinitescrollDemo.$emit('ydui.infinitescroll.finishLoad')表示单次数据请求完毕2.this.$refs.infinitescrollDemo.$emit('ydui.infinitescroll.loadedDone')表示所有数据请求完毕3.this.$refs.infinitescrollDemo.$emit('ydui.infinitescroll.reInit')表示重新初始化2.传入pullcallback参数表示开启下拉刷新功能1.更新成功请调用this.$refs.infinitescrollDemo.$emit('ydui.pullrefresh.finishLoad.success',true) 参数 true 开始提示, false 关闭提示, 默认true2.更新成功请调用this.$refs.infinitescrollDemo.$emit('ydui.pullrefresh.finishLoad.fail',true) 参数 true 开始提示, false 关闭提示, 默认true3.传入pullTipBgColor参数来修改下拉刷新成功状态的背景色,默认蓝色(#171dca)

组件源码,直接copy源码保存为InfiniteScroll.vue即可开始使用

<template><div><!-- 下拉刷新 --><divclass="dragBox"ref="dragBox":style="moveOffset? { transform: `translateY(${moveOffset / double}px)` }: ''"><!-- 下拉刷新动画效果 --><divclass="yd-pullTip":style="{height: `${moveOffset / double}px`,top: `-${moveOffset / double}px`,paddingBottom:moveOffset / double > (touches.distance - 20) / 2? (touches.distance - 20) / 2 + 'px': `${moveOffset / double}px`}"><imgv-if="touches.loading"src="data:image/gif;base64,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"/><imgv-else:class="{ rotate: moveOffset >= touches.distance * double }"src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAA4CAYAAAB3yEEBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OEFFN0NBNENDQkVEMTFFOTg3ODBCRjE0Q0NBQTdFMDEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OEFFN0NBNERDQkVEMTFFOTg3ODBCRjE0Q0NBQTdFMDEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4QUU3Q0E0QUNCRUQxMUU5ODc4MEJGMTRDQ0FBN0UwMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4QUU3Q0E0QkNCRUQxMUU5ODc4MEJGMTRDQ0FBN0UwMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pk84QwMAAALZSURBVHja7Jm7jtpAFIbtgeXiBhvIG6RaEFRcnmIfIc02W20RVtr0KZIiaTYNTV6CJq/ApYpAW21DQwei4SIhQeZf+aDRrD0e20iJlBkJgeZ2vvnPmcN4bK9WKytluRF+D9JMlE0zeLvd3s1ms956vX7ved5zvV5/VywWf8r9bNu2TqdT5Hx2HGXESQEyHo+/73Y7h9odx1m32+2HpEBMB4CKCsSvd0ej0ROvv5Xn0VEmEkaeJAyECup5+7cgoNQwMghf+RkkbLVQaDKZxAZiUa4RVnwLRfb7vaPqR2Wz2bhxFWK6rvFjwdF1Z1QMJXITKaICUe2WODHEomJkOBw+BcVILpc7yP0zmYyVz+cPSRViKpCwGOF5ZNtqtT69mYwxq9vt3hcKhW0ShZjKNSIIFYDwxNbjCW4RNBb1nU6nFwQEhVRALGT7vroGbhFdAwM+SF8lN9oBRC4T51ABMVH+oIQG6UkRGIAhnWyKfuQyOQUQEOyJIcCIXAZBHe0Qcg3/7kfllyCFwlzGE+MXKER2GMWImFnFrSrESF+GiIJCuwwkjiGFeGx+IDfd8GPAx4hg7esmuqB20WXyGC6AO51OH8Hxqgw/RlzLq/Rj5J5AdOJEVXCsCHIZ5l0ul9fnAC6VSnN51wAk6FySpoTFEOwTzKDRaHzlJ7U5dk61Wn2GpDKIbtDq7jLYgb1yufwC++DICh0W3OCBK3SV9iyr4zIeiwvf1vnsnMWKj8cjVj4Qt7P8x6d7jtU9uvLPrzdJjwzLOyCsPm1RzcOSDrwkEBaNT9b6C0XeDATHrH+oGBgDY2AMjIExMAbGwBgYA2NgDMz/BJPk1iI2DD394cZC9dia5NGYJVmxf5NwFed5+iLvm8IMMcYO5Ar6xuWPf8dzho7jtsQxAxjXdefiqj3P+w3FyHBcV6UJ4EGz2fxcqVRe8AID12K1Wu2HleLWy477KjngButir5L/CDAAX5Ad05wlsggAAAAASUVORK5CYII="/>{{ touches.statusText }}</div><!-- 下拉刷新提示效果 --><div class="yd-Tip" v-if="pullupdateStatus">{{ pullupdateText }}<span:style="pullupdateText === '更新成功'? `backgroundColor: ${pullTipBgColor};`: 'backgroundColor: #aeaeae;'"></span></div><slot name="list"></slot></div><div ref="tag" style="height: 0;"></div><div class="yd-list-loading" v-if="!isDone"><div v-show="isLoading"><slot name="loadingTip"><template><svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 100 100"preserveAspectRatio="xMidYMid"class="lds-ellipsis"><circle cx="84" cy="50" r="5.04711" fill="#f3b72e"><animateattributeName="r"values="10;0;0;0;0"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="0s"></animate><animateattributeName="cx"values="84;84;84;84;84"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="0s"></animate></circle><circle cx="66.8398" cy="50" r="10" fill="#E8574E"><animateattributeName="r"values="0;10;10;10;0"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="-0.85s"></animate><animateattributeName="cx"values="16;16;50;84;84"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="-0.85s"></animate></circle><circle cx="32.8398" cy="50" r="10" fill="#43A976"><animateattributeName="r"values="0;10;10;10;0"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="-0.425s"></animate><animateattributeName="cx"values="16;16;50;84;84"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="-0.425s"></animate></circle><circle cx="16" cy="50" r="4.95289" fill="#304153"><animateattributeName="r"values="0;10;10;10;0"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="0s"></animate><animateattributeName="cx"values="16;16;50;84;84"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="0s"></animate></circle><circle cx="16" cy="50" r="0" fill="#f3b72e"><animateattributeName="r"values="0;0;10;10;10"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="0s"></animate><animateattributeName="cx"values="16;16;16;50;84"keyTimes="0;0.25;0.5;0.75;1"keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1"calcMode="spline"dur="1.7s"repeatCount="indefinite"begin="0s"></animate></circle></svg></template></slot></div></div><div class="yd-list-donetip" v-show="!isLoading && isDone"><slot name="doneTip">没有更多数据了</slot></div></div>
</template><script type="text/babel">
export default {name: 'BaseInfinitescroll',data() {return {isLoading: false,isDone: false,num: 1,touches: {loading: false, //是否在下拉刷新回调中distance: 60, //滑动距离大于100时可释放刷新startClientY: 0,currentClientY: Math.pow(2, 32), //当前触摸位置isDraging: false, //是否开始下拉刷新statusText: '下拉刷新' //此时的状态描述},moveOffset: 0, //手指下拉的长度滚动的double: 3, //手滑动距离与下拉距离的倍数// step: 20 //松开手指界面向上滑动的速度time: 100,pullupdateStatus: false, //是否展示下拉刷新更新后的提示pullupdateText: '更新成功' //展示下拉刷新更新后的提示}},props: {callback: {type: Function},pullcallback: {type: Function},distance: {default: 0,validator(val) {return /^\d*$/.test(val)}},scrollTop: {type: Boolean,default: true},pullTipBgColor: {type: String,default: '#1989fa'}},methods: {init() {if (this.scrollTop) {if (this.scrollview === window) {window.scrollTo(0, 0)} else {this.scrollview.scrollTop = 0}}this.scrollview.addEventListener('scroll',this.throttledCheck,false)this.$on('ydui.infinitescroll.loadedDone', () => {this.isLoading = falsethis.isDone = true})this.$on('ydui.infinitescroll.finishLoad', () => {this.isLoading = false})this.$on('ydui.infinitescroll.reInit', () => {this.isLoading = falsethis.isDone = false})},pullinit() {const dragBox = this.$refs.dragBoxdragBox.addEventListener('touchstart', this.touchStartHandler)dragBox.addEventListener('touchmove', this.touchMoveHandler)dragBox.addEventListener('touchend', this.touchEndHandler)//防止微信浏览器下拉出现域名document.body.addEventListener('touchmove', this.stopDragEvent, {passive: false //调用阻止默认行为})//容器距离顶部的距离this.offsetTop = this.$refs.dragBox.getBoundingClientRect().top//上拉加载完成this.$on('ydui.pullrefresh.finishLoad.success', (tip = true) => {this.pullupdateText = '更新成功'this.Retract(0, tip)})this.$on('ydui.pullrefresh.finishLoad.fail', (tip = true) => {this.pullupdateText = '更新失败'this.Retract(0, tip)})// eslint-disable-next-line no-unused-varsthis.$on('ydui.pullrefresh.finishLoad.load', (tip = true) => {this.touches.statusText = '加载中'this.touches.loading = truethis.moveOffset = this.double * this.touches.distancethis.pullupdateStatus = false})},scrollHandler() {if (this.isLoading || this.isDone) returnconst scrollview = this.scrollviewconst contentHeight = document.body.offsetHeightconst isWindow = scrollview === windowconst offsetTop = isWindow? 0: scrollview.getBoundingClientRect().topconst scrollviewHeight = isWindow? contentHeight: scrollview.offsetHeightif (!scrollview) {// eslint-disable-next-lineconsole.warn("Can't find the scrollview!")return}if (!this.$refs.tag) {// eslint-disable-next-lineconsole.warn("Can't find the refs.tag!")return}const tagOffsetTop =Math.floor(this.$refs.tag.getBoundingClientRect().top) - 1const distance =!!this.distance && this.distance > 0? ~~this.distance: Math.floor(contentHeight / 10)if (tagOffsetTop > offsetTop &&tagOffsetTop - (distance + offsetTop) * this.num <=contentHeight &&this.$el.offsetHeight > scrollviewHeight) {this.isLoading = truethis.callback && this.callback()this.num++}},throttle(method, context) {clearTimeout(method.tId)method.tId = setTimeout(() => {method.call(context)}, 30)},throttledCheck() {this.throttle(this.scrollHandler)},getScrollview(el) {let currentNode = elwhile (currentNode &&currentNode.tagName !== 'HTML' &&currentNode.tagName !== 'BODY' &&currentNode.nodeType === 1) {let overflowY = document.defaultView.getComputedStyle(currentNode).overflowYif (overflowY === 'scroll' || overflowY === 'auto') {return currentNode}currentNode = currentNode.parentNode}return window},/*** 获取滚动的位置* @method getScrollTop* @return {Number} 滚动的位置*/getScrollTop(element) {if (element === window) {return Math.max(window.pageYOffset || 0,document.documentElement.scrollTop)} else {return element.scrollTop}},touchStartHandler(event) {//正在执行下拉刷新则返回if (this.touches.loading) {event.preventDefault()return}//当向下滚动了则直接返回if (this.getScrollTop(this.scrollview) > 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {return}//数据初始化this.touches.loading = falsethis.touches.startClientY = 0this.touches.isDraging = falsethis.touches.statusText = '下拉刷新'this.moveOffset = 0//记录触摸位置// this.touches.startClientX = event.touches[0].clientXthis.touches.startClientY = event.touches[0].clientY},touchMoveHandler(event) {const touches = this.touches//记录当前触摸位置touches.currentClientY = event.touches[0].clientY//当向下滚动了则直接返回if (this.getScrollTop(this.scrollview) > 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {// this.dragTip.translate = 0;// this.resetParams();this.touches.isDraging = falsethis.moveOffset = 0return}//正在执行下拉刷新则返回if (this.touches.loading) {event.preventDefault()return}// console.log(this.getScrollTop(this.scrollview))// console.log('执行了')const currentY = event.touches[0].clientY// const currentX = event.touches[0].clientX//防止手指直接下滑造成页面不能正常的滚动if (!touches.isDraging && currentY < touches.startClientY) {return}//手指先先下拉,再向上滑,说明此时手指已经在触摸位置上方了if (touches.isDraging &&(currentY - touches.startClientY < 0 ||this.$refs.dragBox.getBoundingClientRect().top <this.offsetTop)) {// this.isDragToUp = true;event.preventDefault()return}//手指向下滑if (touches.isDraging && this.getScrollTop(this.scrollview) === 0) {event.preventDefault()}// //开始下拉刷新this.touches.isDraging = true// const touchAngle =//     (Math.atan2(//         Math.abs(currentY - touches.startClientY),//         Math.abs(currentX - touches.startClientX)//     ) *//         180) ///     Math.PI// if (90 - touchAngle > 45) return//手指滑动的距离let deltaSlide = currentY - touches.startClientY//如果超过了指定的距离, 达到了释放更新的条件if (deltaSlide >= touches.distance * this.double) {this.touches.statusText = '释放更新'} else {this.touches.statusText = '下拉刷新'}//记录滑动的位置this.moveOffset = deltaSlide// console.log(this.moveOffset)},touchEndHandler(event) {const touches = this.touches// console.log(this.touches.isDraging)//正在执行下拉刷新则返回if (this.touches.loading) {event.preventDefault()return}//当向下滚动了则直接返回if (this.getScrollTop(this.scrollview) > 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {this.touches.isDraging = falsethis.moveOffset = 0return}const currentY = event.changedTouches[0].clientY// const currentX = event.changedTouches[0].clientX//说明此时手指已经在触摸位置上方了if (currentY - touches.startClientY < 0 ||this.$refs.dragBox.getBoundingClientRect().top < this.offsetTop) {this.touches.isDraging = falseevent.preventDefault()return}//下拉刷新阻止浏览器默认行为if (this.getScrollTop(this.scrollview) === 0 &&currentY !== this.touches.startClientY) {event.preventDefault()}//手指滑动的距离let deltaSlide = currentY - touches.startClientY//如果超过了指定的距离if (deltaSlide >= touches.distance * this.double) {//进行更新的动画this.touches.statusText = '加载中'// alert('下拉刷新')this.touches.startClientY = 0this.touches.isDraging = falsethis.Retract(touches.distance * this.double)return} else {this.touches.isDraging = false//距离不够则不刷新this.Retract(0, false)}},stopDragEvent(event) {this.touches.isDraging && event.preventDefault()},Retract(offsetTop, tip) {let timer = setInterval(() => {//根据时间计算出每次运动的距离// 总时间 / 每次运动时间 = 运动次数// 总长度 / 运动次数 = 每次运动距离let step = ((this.touches.distance * this.double) /((this.time * 60) / 1000).toFixed(2)).toFixed(2)if (this.moveOffset - step > offsetTop) {this.moveOffset -= step} else {this.moveOffset = offsetTopclearInterval(timer)if (offsetTop !== 0) {this.touches.loading = truethis.pullcallback && this.pullcallback()} else {//重置this.touches.loading = falsethis.touches.startClientY = 0this.touches.isDraging = falsethis.touches.statusText = '下拉刷新'//执行加载中动画if (tip) {//执行更新成功或者失败动画this.pullupdateStatus = truesetTimeout(() => {this.pullupdateStatus = false}, 1000)}}}}, 1000 / 60)}},mounted() {// console.log(this.distance, this.touches)this.scrollview = this.getScrollview(this.$el)if (this.callback) {this.init()}if (this.pullcallback) {this.pullinit()}},beforeDestroy() {this.scrollview.removeEventListener('scroll', this.throttledCheck)this.$refs.dragBox.removeEventListener('touchstart',this.touchStartHandler)this.$refs.dragBox.removeEventListener('touchmove',this.touchMoveHandler)this.$refs.dragBox.removeEventListener('touchend', this.touchEndHandler)document.body.removeEventListener('touchmove', this.stopDragEvent)}
}
</script><style lang="scss" scoped>
@keyframes intact {0% {border-radius: 50%;}100% {border-radius: 0%;}
}
.dragBox {position: relative;
}
.yd {&-list-loading {padding: 0.1rem 0;text-align: center;font-size: 0.26rem;color: #999;height: 0.66rem;box-sizing: content-box;&-box {height: 0.66rem;overflow: hidden;line-height: 0.66rem;}img {height: 0.66rem;display: inline-block;}svg {width: 0.66rem;height: 0.66rem;}}&-list-donetip {font-size: 0.24rem;text-align: center;padding: 0.25rem 0;color: #777;}&-pullTip {text-align: center;font-size: 0.24rem;position: absolute;left: 0;right: 0;background: #eeeeee;color: #a5a5a5;overflow: hidden;display: flex;align-items: flex-end;justify-content: center;img {height: 20px;margin-right: 12px;margin-bottom: -1px;transition: transform 0.1s linear;transform: rotate(180deg);}img.rotate {transform: rotate(0deg);}}&-Tip {z-index: 99999999;position: absolute;left: 0;right: 0;top: 0;height: 36px;line-height: 36px;font-size: 0.26rem;overflow: hidden;text-align: center;color: #fff;span {position: absolute;top: 0;left: 0;z-index: -1;display: block;width: 100%;padding-top: 100%;border-radius: 50%;animation: intact 0.1s linear forwards;}}
}
</style>

移动端下拉刷新,兼容ios,Android及微信浏览器相关推荐

  1. html下拉刷新原理,科技常识:移动端下拉刷新头实现原理及代码实现

    科技常识:移动端下拉刷新头实现原理及代码实现 2021-05-24 21:23:20 • 互联网 今天小编跟大家讲解下有关移动端下拉刷新头实现原理及代码实现 ,相信小伙伴们对这个话题应该有所关注吧,小 ...

  2. 移动端下拉刷新与页面内滑动冲突问题 iscroll 固定行表头 苹果下拉反弹

    此方法在安卓6的自带下拉刷新的APP内无效. 做移动app项目时,用的是app内嵌webview的方法,遇到问题. 在安卓6中,app原生自带下拉刷新,与界面内的下滑冲突. 开始考虑通过在touchm ...

  3. vue移动端下拉刷新、上拉加载

    由于自身的项目比较简单,只有几个H5页面,用来嵌入app中,所有没有引入移动端的UI框架,但是介于能让用户在浏览H5页面时有下拉刷新和上拉加载,有更好的用户体验,自己写组件实现. 1.下拉刷新Drop ...

  4. ios微信小程序下拉刷新怎么配_[wx]微信小程序自定义下拉刷新

    需求: 在小程序内存在列表等形式的页面内增加下拉刷新功能,提高用户体验感,加强界面操作与交互性: 实现方法: 1.小程序提供的下拉刷新(无法自定义刷新动画) 在页面设置内开启下拉(单独页面设置): { ...

  5. 移动端下拉刷新原理和实例

    移动端的下拉刷新是一个很常见的功能,也有许多开源库实现了这个功能,不过为了学习,还是先自己写一个例子学习一下.其中用到了一些touch事件和一些DOM属性CSS3属性.直接上代码,代码里面有注释. & ...

  6. android 下拉刷新实现方式,Android RecyclerView设置下拉刷新的实现方法

    Android RecyclerView设置下拉刷新的实现方法 1 集成 SwipeRefreshLayout 1.1 xml布局文件中使用 android:id="@+id/refresh ...

  7. [ vant ] vue移动端下拉刷新组件

    学习关键语句: vant list组件和下拉刷新 vant 下拉刷新和局部滚动冲突 写在前面 每一次 ! 我是说每一次我在使用 vant 组件库里面 list组件和下拉刷新连在一起用的时候 都会出现下 ...

  8. 移动端下拉刷新infinitescroll

    使用的是infinitescroll 移动端手机下拉刷新数据 第一步:在serviceList.html导入 <script src="/js/jquery.infinitescrol ...

  9. android下拉刷新的方法,Android App使用RecyclerView实现上拉和下拉刷新的方法

    关于recyclerview recyclerview在android 5.0以来被引入,以前经常使用的listview 继承的是abslistview,而recyclerview则直接继承 view ...

最新文章

  1. Linux下搭建DNS服务器
  2. python基础系列:类
  3. Java IO流之缓冲流
  4. 使用ASP.NET上传图片汇总
  5. pytorch,onnx和tensorrt 的速度对比
  6. C++::增加目标码(object code)的大小的操作
  7. 记一次spirngMVC整合HttpPrinter的过程
  8. mac 下载appium
  9. 病毒名称:Hacktool (正当追杀+旁门左道)
  10. 使用python操作新浪微博和一些想法
  11. 用高斯约当法求逆矩阵​​​​​​​​​​​​​​​​​​​​​A-1
  12. ios友盟错误_iOS之DYSM分析友盟错误信息
  13. win10计算机运行在哪里,Win10运行在哪(开始、快捷键、所有应用)
  14. 【计算理论】计算复杂性 ( NP 完全问题 - 布尔可满足性问题 ★ | 布尔可满足性问题是 NP 完全问题证明思路 ) ★
  15. office plus
  16. python二分法排序_「二分法排序」插入排序之二分法插入排序 - seo实验室
  17. 曲线任意里程中边桩坐标正反算(CASIO fx-4850P计算器)程序第四次修改版
  18. 服务器架构的演变过程
  19. 基于Django以及requests爬虫的音乐网站
  20. uni-app uni.uploadFile上传图片前后端(java)详解

热门文章

  1. 网络安全_密码学实验_非对称加密算法RSA
  2. flink cdc 2.2.1 mysql connector
  3. 学习Gluster创建不同卷,设置NFS挂载
  4. 微信小程序语音识别java_微信小程序实现语音识别功能
  5. 来套近乎啊——思维导图
  6. USB总线-Linux内核USB3.0设备控制器之dwc3 gadget驱动初始化过程分析(五)
  7. 音质好的蓝牙耳机有哪几款?公认音质好的蓝牙耳机
  8. zabbix 内存溢出 解决
  9. boost::stacktrace::detail相关的测试程序
  10. 【NOI2015模拟YDC】游戏