You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
9.7 KiB
268 lines
9.7 KiB
// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响 |
|
// https://uniapp.dcloud.io/frame?id=wxs |
|
// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html |
|
|
|
// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致 |
|
var me = {} |
|
|
|
// ------ 自定义下拉刷新动画 start ------ |
|
|
|
/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */ |
|
me.onMoving = function (ins, rate, downHight){ |
|
ins.requestAnimationFrame(function () { |
|
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题 |
|
'transform': 'translateY(' + downHight + 'px)', |
|
'transition': '' |
|
}) |
|
// 环形进度条 |
|
var progress = ins.selectComponent('.mescroll-wxs-progress') |
|
progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'}) |
|
}) |
|
} |
|
|
|
/* 显示下拉刷新进度 */ |
|
me.showLoading = function (ins){ |
|
me.downHight = me.optDown.offset |
|
ins.requestAnimationFrame(function () { |
|
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
'will-change': 'auto', |
|
'transform': 'translateY(' + me.downHight + 'px)', |
|
'transition': 'transform 300ms' |
|
}) |
|
}) |
|
} |
|
|
|
/* 结束下拉 */ |
|
me.endDownScroll = function (ins){ |
|
me.downHight = 0; |
|
me.isDownScrolling = false; |
|
ins.requestAnimationFrame(function () { |
|
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
'will-change': 'auto', |
|
'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空) |
|
'transition': 'transform 300ms' |
|
}) |
|
}) |
|
} |
|
|
|
/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */ |
|
me.clearTransform = function (ins){ |
|
ins.requestAnimationFrame(function () { |
|
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
'will-change': '', |
|
'transform': '', |
|
'transition': '' |
|
}) |
|
}) |
|
} |
|
|
|
// ------ 自定义下拉刷新动画 end ------ |
|
|
|
/** |
|
* 监听逻辑层数据的变化 (实时更新数据) |
|
*/ |
|
function propObserver(wxsProp) { |
|
me.optDown = wxsProp.optDown |
|
me.scrollTop = wxsProp.scrollTop |
|
me.bodyHeight = wxsProp.bodyHeight |
|
me.isDownScrolling = wxsProp.isDownScrolling |
|
me.isUpScrolling = wxsProp.isUpScrolling |
|
me.isUpBoth = wxsProp.isUpBoth |
|
me.isScrollBody = wxsProp.isScrollBody |
|
me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确 |
|
} |
|
|
|
/** |
|
* 监听逻辑层数据的变化 (调用wxs的方法) |
|
*/ |
|
function callObserver(callProp, oldValue, ins) { |
|
if (me.disabled()) return; |
|
if(callProp.callType){ |
|
// 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style |
|
if(callProp.callType === 'showLoading'){ |
|
me.showLoading(ins) |
|
}else if(callProp.callType === 'endDownScroll'){ |
|
me.endDownScroll(ins) |
|
}else if(callProp.callType === 'clearTransform'){ |
|
me.clearTransform(ins) |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* touch事件 |
|
*/ |
|
function touchstartEvent(e, ins) { |
|
me.downHight = 0; // 下拉的距离 |
|
me.startPoint = me.getPoint(e); // 记录起点 |
|
me.startTop = me.getScrollTop(); // 记录此时的滚动条位置 |
|
me.startAngle = 0; // 初始角度 |
|
me.lastPoint = me.startPoint; // 重置上次move的点 |
|
me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况) |
|
me.inTouchend = false; // 标记不是touchend |
|
|
|
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步) |
|
} |
|
|
|
function touchmoveEvent(e, ins) { |
|
var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效) |
|
|
|
if (me.disabled()) return isPrevent; |
|
|
|
var scrollTop = me.getScrollTop(); // 当前滚动条的距离 |
|
var curPoint = me.getPoint(e); // 当前点 |
|
|
|
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 |
|
|
|
// 向下拉 && 在顶部 |
|
// mescroll-body,直接判定在顶部即可 |
|
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove |
|
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等 |
|
if (moveY > 0 && ( |
|
(me.isScrollBody && scrollTop <= 0) |
|
|| |
|
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) ) |
|
)) { |
|
// 可下拉的条件 |
|
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && |
|
me.isUpBoth))) { |
|
|
|
// 下拉的角度是否在配置的范围内 |
|
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90] |
|
if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新 |
|
|
|
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发 |
|
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) { |
|
me.inTouchend = true; // 标记执行touchend |
|
touchendEvent(e, ins); // 提前触发touchend |
|
return isPrevent; |
|
} |
|
|
|
isPrevent = false // 小程序是return false |
|
|
|
var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上) |
|
|
|
// 下拉距离 < 指定距离 |
|
if (me.downHight < me.optDown.offset) { |
|
if (me.movetype !== 1) { |
|
me.movetype = 1; // 加入标记,保证只执行一次 |
|
// me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次 |
|
me.callMethod(ins, {type: 'setLoadType', downLoadType: 1}) |
|
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 |
|
} |
|
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小 |
|
|
|
// 指定距离 <= 下拉距离 |
|
} else { |
|
if (me.movetype !== 2) { |
|
me.movetype = 2; // 加入标记,保证只执行一次 |
|
// me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次 |
|
me.callMethod(ins, {type: 'setLoadType', downLoadType: 2}) |
|
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 |
|
} |
|
if (diff > 0) { // 向下拉 |
|
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小 |
|
} else { // 向上收 |
|
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度 |
|
} |
|
} |
|
|
|
me.downHight = Math.round(me.downHight) // 取整 |
|
var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值 |
|
// me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行 |
|
me.onMoving(ins, rate, me.downHight) |
|
} |
|
} |
|
|
|
me.lastPoint = curPoint; // 记录本次移动的点 |
|
|
|
return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效) |
|
} |
|
|
|
function touchendEvent(e, ins) { |
|
// 如果下拉区域高度已改变,则需重置回来 |
|
if (me.isMoveDown) { |
|
if (me.downHight >= me.optDown.offset) { |
|
// 符合触发刷新的条件 |
|
me.downHight = me.optDown.offset; // 更新下拉区域高度 |
|
// me.triggerDownScroll(); |
|
me.callMethod(ins, {type: 'triggerDownScroll'}) |
|
} else { |
|
// 不符合的话 则重置 |
|
me.downHight = 0; |
|
// me.optDown.endDownScroll && me.optDown.endDownScroll(me); |
|
me.callMethod(ins, {type: 'endDownScroll'}) |
|
} |
|
me.movetype = 0; |
|
me.isMoveDown = false; |
|
} else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件 |
|
var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 |
|
// 上滑 |
|
if (isScrollUp) { |
|
// 需检查滑动的角度 |
|
var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90] |
|
if (angle > 80) { |
|
// 检查并触发上拉 |
|
// me.triggerUpScroll(true); |
|
me.callMethod(ins, {type: 'triggerUpScroll'}) |
|
} |
|
} |
|
} |
|
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步) |
|
} |
|
|
|
/* 是否禁用下拉刷新 */ |
|
me.disabled = function(){ |
|
return !me.optDown || !me.optDown.use || me.optDown.native |
|
} |
|
|
|
/* 根据点击滑动事件获取第一个手指的坐标 */ |
|
me.getPoint = function(e) { |
|
if (!e) { |
|
return {x: 0,y: 0} |
|
} |
|
if (e.touches && e.touches[0]) { |
|
return {x: e.touches[0].pageX,y: e.touches[0].pageY} |
|
} else if (e.changedTouches && e.changedTouches[0]) { |
|
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY} |
|
} else { |
|
return {x: e.clientX,y: e.clientY} |
|
} |
|
} |
|
|
|
/* 计算两点之间的角度: 区间 [0,90]*/ |
|
me.getAngle = function (p1, p2) { |
|
var x = Math.abs(p1.x - p2.x); |
|
var y = Math.abs(p1.y - p2.y); |
|
var z = Math.sqrt(x * x + y * y); |
|
var angle = 0; |
|
if (z !== 0) { |
|
angle = Math.asin(y / z) / Math.PI * 180; |
|
} |
|
return angle |
|
} |
|
|
|
/* 获取滚动条的位置 */ |
|
me.getScrollTop = function() { |
|
return me.scrollTop || 0 |
|
} |
|
|
|
/* 获取body的高度 */ |
|
me.getBodyHeight = function() { |
|
return me.bodyHeight || 0; |
|
} |
|
|
|
/* 调用逻辑层的方法 */ |
|
me.callMethod = function(ins, param) { |
|
if(ins) ins.callMethod('wxsCall', param) |
|
} |
|
|
|
/* 导出模块 */ |
|
module.exports = { |
|
propObserver: propObserver, |
|
callObserver: callObserver, |
|
touchstartEvent: touchstartEvent, |
|
touchmoveEvent: touchmoveEvent, |
|
touchendEvent: touchendEvent |
|
} |