40 changed files with 5050 additions and 83 deletions
-
3README.md
-
0uni_modules/cp-tpl/changelog.md
-
80uni_modules/cp-tpl/package.json
-
1uni_modules/cp-tpl/pages/readme.txt
-
1uni_modules/cp-tpl/readme.md
-
6uni_modules/mescroll-uni/changelog.md
-
19uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
-
400uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
-
47uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
-
39uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
-
360uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
-
49uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
-
437uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
-
44uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
-
53uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
-
32uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
-
40uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
-
380uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
-
64uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
-
462uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
-
116uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
-
55uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
-
47uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
-
83uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
-
47uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
-
39uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
-
15uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
-
57uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
-
64uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
-
36uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css
-
799uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js
-
477uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
-
47uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
-
66uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
-
74uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
-
109uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js
-
92uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
-
268uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
-
80uni_modules/mescroll-uni/package.json
-
45uni_modules/mescroll-uni/readme.md
@ -1,80 +0,0 @@ |
|||
{ |
|||
"id": "cp-tpl", |
|||
"displayName": "cp-tpl", |
|||
"version": "1.0.0", |
|||
"description": "cp-tpl", |
|||
"keywords": [ |
|||
"cp-tpl" |
|||
], |
|||
"repository": "", |
|||
"engines": { |
|||
"HBuilderX": "^3.1.0" |
|||
}, |
|||
"dcloudext": { |
|||
"category": [ |
|||
"前端页面模板", |
|||
"前端页面模板" |
|||
], |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "", |
|||
"data": "", |
|||
"permissions": "" |
|||
}, |
|||
"npmurl": "" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": [], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "u", |
|||
"aliyun": "u" |
|||
}, |
|||
"client": { |
|||
"Vue": { |
|||
"vue2": "u", |
|||
"vue3": "u" |
|||
}, |
|||
"App": { |
|||
"app-vue": "u", |
|||
"app-nvue": "u" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "u", |
|||
"Android Browser": "u", |
|||
"微信浏览器(Android)": "u", |
|||
"QQ浏览器(Android)": "u" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "u", |
|||
"IE": "u", |
|||
"Edge": "u", |
|||
"Firefox": "u", |
|||
"Safari": "u" |
|||
}, |
|||
"小程序": { |
|||
"微信": "u", |
|||
"阿里": "u", |
|||
"百度": "u", |
|||
"字节跳动": "u", |
|||
"QQ": "u" |
|||
}, |
|||
"快应用": { |
|||
"华为": "u", |
|||
"联盟": "u" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
页面模板 |
@ -1 +0,0 @@ |
|||
# cp-tpl |
@ -0,0 +1,6 @@ |
|||
## 1.3.7(2021-04-13) |
|||
1. 新增`mescroll-swiper-sticky.vue`的示例, 轮播吸顶菜单导航 |
|||
2. 新增`mescroll-empty.vue`的示例, 单独使用空布局组件 |
|||
3. 简化tabs在具体项目中的使用,并简化对应的示例 |
|||
4. mescroll-uni 支持动态禁止滚动的属性 disableScroll (注: mescroll-body不支持) |
|||
-by 小瑾同学 |
@ -0,0 +1,19 @@ |
|||
.mescroll-body { |
|||
position: relative; /* 下拉刷新区域相对自身定位 */ |
|||
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/ |
|||
overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */ |
|||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ |
|||
} |
|||
|
|||
/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */ |
|||
.mescroll-body.mescorll-sticky{ |
|||
overflow: unset !important |
|||
} |
|||
|
|||
/* 适配 iPhoneX */ |
|||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|||
.mescroll-safearea { |
|||
padding-bottom: constant(safe-area-inset-bottom); |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
} |
|||
} |
@ -0,0 +1,400 @@ |
|||
<template> |
|||
<view |
|||
class="mescroll-body mescroll-render-touch" |
|||
:class="{'mescorll-sticky': sticky}" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp" |
|||
> |
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view> |
|||
<view class="downwarp-tip">{{downText}}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from "../mescroll-uni/wxs/renderjs.js"; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
// 引入mescroll-uni.js,处理核心逻辑 |
|||
import MeScroll from "../mescroll-uni/mescroll-uni.js"; |
|||
// 引入全局配置 |
|||
import GlobalOption from "../mescroll-uni/mescroll-uni-option.js"; |
|||
// 引入国际化工具类 |
|||
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js'; |
|||
// 引入回到顶部组件 |
|||
import MescrollTop from "../mescroll-uni/components/mescroll-top.vue"; |
|||
// 引入兼容wxs(含renderjs)写法的mixins |
|||
import WxsMixin from "../mescroll-uni/wxs/mixins.js"; |
|||
|
|||
/** |
|||
* mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好 |
|||
* @property {Object} down 下拉刷新的参数配置 |
|||
* @property {Object} up 上拉加载的参数配置 |
|||
* @property {Object} i18n 国际化的参数配置 |
|||
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true |
|||
* @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效) |
|||
* @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏 |
|||
* @event {Function} init 初始化完成的回调 |
|||
* @event {Function} down 下拉刷新的回调 |
|||
* @event {Function} up 上拉加载的回调 |
|||
* @event {Function} emptyclick 点击empty配置的btnText按钮回调 |
|||
* @event {Function} topclick 点击回到顶部的按钮回调 |
|||
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效) |
|||
* @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body> |
|||
*/ |
|||
export default { |
|||
name: 'mescroll-body', |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
props: { |
|||
down: Object, |
|||
up: Object, |
|||
i18n: Object, |
|||
top: [String, Number], |
|||
topbar: [Boolean, String], |
|||
bottom: [String, Number], |
|||
safearea: Boolean, |
|||
height: [String, Number], |
|||
bottombar:{ |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
sticky: Boolean |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: {optDown:{},optUp:{}}, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
}; |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight(){ |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset() { |
|||
return this.downLoadType === 3 || this.downLoadType === 4; |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : ''; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return 'rotate(' + 360 * this.downRate + 'deg)' |
|||
}, |
|||
// 文本提示 |
|||
downText(){ |
|||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错 |
|||
switch (this.downLoadType){ |
|||
case 1: return this.mescroll.optDown.textInOffset; |
|||
case 2: return this.mescroll.optDown.textOutOffset; |
|||
case 3: return this.mescroll.optDown.textLoading; |
|||
case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset; |
|||
default: return this.mescroll.optDown.textInOffset; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num) { |
|||
if (typeof num === 'string') { |
|||
if (num.indexOf('px') !== -1) { |
|||
if (num.indexOf('rpx') !== -1) { |
|||
// "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if (num.indexOf('upx') !== -1) { |
|||
// "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { |
|||
// "10px" |
|||
return Number(num.replace('px', '')); |
|||
} |
|||
} else if (num.indexOf('%') !== -1) { |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace('%', '')) / 100; |
|||
return this.windowHeight * rate; |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0; |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll); |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
beforeEndDownScroll(mescroll){ |
|||
vm.downLoadType = 4; |
|||
return mescroll.optDown.beforeEndDelay // 延时结束的时长 |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时 |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|||
if(vm.downLoadType === 4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
if(typeof y === 'string'){ |
|||
// 滚动到指定view (y为css选择器) |
|||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let top = rect.top |
|||
top += vm.mescroll.getScrollTop() |
|||
uni.pageScrollTo({ |
|||
scrollTop: top, |
|||
duration: t |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
},30) |
|||
} else{ |
|||
// 滚动到指定位置 (y必须为数字) |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../mescroll-body/mescroll-body.css"; |
|||
@import "../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../mescroll-uni/components/mescroll-up.css"; |
|||
</style> |
@ -0,0 +1,47 @@ |
|||
/*下拉刷新--标语*/ |
|||
.mescroll-downwarp .downwarp-slogan{ |
|||
display: block; |
|||
width: 420rpx; |
|||
height: 168rpx; |
|||
margin: auto; |
|||
} |
|||
/*下拉刷新--向下进度动画*/ |
|||
.mescroll-downwarp .downwarp-progress{ |
|||
display: inline-block; |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
border: none; |
|||
margin: auto; |
|||
background-size: contain; |
|||
background-repeat: no-repeat; |
|||
background-position: center; |
|||
background-image: url(https://www.mescroll.com/img/beibei/mescroll-progress.png); |
|||
transition: all 300ms; |
|||
} |
|||
/*下拉刷新--进度条*/ |
|||
.mescroll-downwarp .downwarp-loading{ |
|||
display: inline-block; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid #FF8095; |
|||
border-bottom-color: transparent; |
|||
} |
|||
/*下拉刷新--吉祥物*/ |
|||
.mescroll-downwarp .downwarp-mascot{ |
|||
position: absolute; |
|||
right: 16rpx; |
|||
bottom: 0; |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
background-size: contain; |
|||
background-repeat: no-repeat; |
|||
animation: animMascot .6s steps(1,end) infinite; |
|||
} |
|||
@keyframes animMascot { |
|||
0% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)} |
|||
25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)} |
|||
50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)} |
|||
75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)} |
|||
100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)} |
|||
} |
@ -0,0 +1,39 @@ |
|||
<!-- 下拉刷新区域 --> |
|||
<template> |
|||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
<view class="downwarp-mascot"></view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object , // down的配置项 |
|||
type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption(){ |
|||
return this.option || {} |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.type === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "./mescroll-down.css"; |
|||
</style> |
@ -0,0 +1,360 @@ |
|||
<template> |
|||
<view |
|||
class="mescroll-body mescroll-render-touch" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" |
|||
:class="{'mescorll-sticky': sticky}" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp" |
|||
> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
<view class="downwarp-mascot"></view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
}; |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏 |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight(){ |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset() { |
|||
return this.downLoadType === 3 || this.downLoadType === 4; |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : ''; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num) { |
|||
if (typeof num === 'string') { |
|||
if (num.indexOf('px') !== -1) { |
|||
if (num.indexOf('rpx') !== -1) { |
|||
// "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if (num.indexOf('upx') !== -1) { |
|||
// "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { |
|||
// "10px" |
|||
return Number(num.replace('px', '')); |
|||
} |
|||
} else if (num.indexOf('%') !== -1) { |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace('%', '')) / 100; |
|||
return this.windowHeight * rate; |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0; |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll); |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时 |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|||
if(vm.downLoadType === 4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
if(typeof y === 'string'){ |
|||
// 滚动到指定view (y为css选择器) |
|||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let top = rect.top |
|||
top += vm.mescroll.getScrollTop() |
|||
uni.pageScrollTo({ |
|||
scrollTop: top, |
|||
duration: t |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
},30) |
|||
} else{ |
|||
// 滚动到指定位置 (y必须为数字) |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-body/mescroll-body.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
</style> |
@ -0,0 +1,49 @@ |
|||
// mescroll-uni和mescroll-body 的全局配置
|
|||
const GlobalOption = { |
|||
down: { |
|||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
|
|||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
}, |
|||
up: { |
|||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 150, // 距底部多远时,触发upCallback
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
|||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
|||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
}, |
|||
empty: { |
|||
use: true, // 是否显示空布局
|
|||
icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|||
} |
|||
}, |
|||
// 国际化配置
|
|||
i18n: { |
|||
// 中文
|
|||
zh: { |
|||
up: { |
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
|||
empty: { |
|||
tip: '~ 暂无相关数据 ~' // 空提示
|
|||
} |
|||
} |
|||
}, |
|||
// 英文
|
|||
en: { |
|||
up: { |
|||
textLoading: 'loading ...', |
|||
textNoMore: '-- END --', |
|||
empty: { |
|||
tip: '~ absolutely empty ~' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default GlobalOption |
@ -0,0 +1,437 @@ |
|||
<template> |
|||
<view class="mescroll-uni-warp"> |
|||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false"> |
|||
<view class="mescroll-uni-content mescroll-render-touch" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp"> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
<view class="downwarp-mascot"></view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素) |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
scrollTop: 0, // 滚动条的位置 |
|||
scrollAnim: false, // 是否开启滚动动画 |
|||
windowTop: 0, // 可使用窗口的顶部位置 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
} |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
disableScroll: Boolean // 是否禁止滚动 |
|||
}, |
|||
computed: { |
|||
// 是否使用fixed定位 (当height有值,则不使用) |
|||
isFixed(){ |
|||
return !this.height && this.fixed |
|||
}, |
|||
// mescroll的高度 |
|||
scrollHeight(){ |
|||
if (this.isFixed) { |
|||
return "auto" |
|||
} else if(this.height){ |
|||
return this.toPx(this.height) + 'px' |
|||
}else{ |
|||
return "100%" |
|||
} |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
fixedTop() { |
|||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0 |
|||
}, |
|||
padTop() { |
|||
return !this.isFixed ? this.numTop + 'px' : 0 |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom) |
|||
}, |
|||
fixedBottom() { |
|||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0 |
|||
}, |
|||
padBottom() { |
|||
return !this.isFixed ? this.numBottom + 'px' : 0 |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset(){ |
|||
return this.downLoadType===3 || this.downLoadType===4 |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : '' |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 列表是否可滑动 |
|||
scrollable(){ |
|||
if(this.disableScroll) return false |
|||
return this.downLoadType===0 || this.isDownReset |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num){ |
|||
if(typeof num === "string"){ |
|||
if (num.indexOf('px') !== -1) { |
|||
if(num.indexOf('rpx') !== -1) { // "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if(num.indexOf('upx') !== -1) { // "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { // "10px" |
|||
return Number(num.replace('px', '')) |
|||
} |
|||
}else if (num.indexOf('%') !== -1){ |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace("%","")) / 100 |
|||
return this.windowHeight * rate |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0 |
|||
}, |
|||
//注册列表滚动事件,用于下拉刷新和上拉加载 |
|||
scroll(e) { |
|||
this.mescroll.scroll(e.detail, () => { |
|||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动 |
|||
}) |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll) |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
}, |
|||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页) |
|||
setClientHeight() { |
|||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) { |
|||
this.isExec = true; // 避免多次获取 |
|||
this.$nextTick(() => { // 确保dom已渲染 |
|||
this.getClientInfo(data=>{ |
|||
this.isExec = false; |
|||
if (data) { |
|||
this.mescroll.setClientHeight(data.height); |
|||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次 |
|||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1; |
|||
setTimeout(() => { |
|||
this.setClientHeight() |
|||
}, this.clientNum * 100) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
}, |
|||
// 获取滚动区域的信息 |
|||
getClientInfo(success){ |
|||
let query = uni.createSelectorQuery(); |
|||
// #ifndef MP-ALIPAY || MP-DINGTALK |
|||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值 |
|||
// #endif |
|||
let view = query.select('#' + this.viewId); |
|||
view.boundingClientRect(data => { |
|||
success(data) |
|||
}).exec(); |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downResetTimer && clearTimeout(vm.downResetTimer) |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整 |
|||
if(vm.downLoadType===4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll) |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
// 更新容器的高度 (多mescroll的情况) |
|||
vm.setClientHeight() |
|||
} |
|||
} |
|||
} |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption); |
|||
vm.mescroll.viewId = vm.viewId; // 附带id |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if(sys.windowTop) vm.windowTop = sys.windowTop; |
|||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是scrollview,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡 |
|||
if(typeof y === 'string'){ |
|||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现 |
|||
vm.getClientInfo(function(rect){ |
|||
let mescrollTop = rect.top // mescroll到顶部的距离 |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let curY = vm.mescroll.getScrollTop() |
|||
let top = rect.top - mescrollTop |
|||
top += curY |
|||
if(!vm.isFixed) top -= vm.numTop |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = top |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
}) |
|||
return; |
|||
} |
|||
let curY = vm.mescroll.getScrollTop() |
|||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡 |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = y |
|||
}) |
|||
} else { |
|||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t |
|||
vm.scrollTop = step |
|||
}, t) |
|||
} |
|||
}) |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
mounted() { |
|||
// 设置容器的高度 |
|||
this.setClientHeight() |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-uni/mescroll-uni.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
</style> |
@ -0,0 +1,44 @@ |
|||
/*下拉刷新--上下箭头*/ |
|||
.mescroll-downwarp .downwarp-arrow { |
|||
display: inline-block; |
|||
width: 20px; |
|||
height: 20px; |
|||
margin: 10px; |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png); |
|||
background-size: contain; |
|||
vertical-align: middle; |
|||
transition: all 300ms; |
|||
} |
|||
|
|||
/*下拉刷新--旋转进度条*/ |
|||
.mescroll-downwarp .downwarp-progress{ |
|||
width: 36px; |
|||
height: 36px; |
|||
border: none; |
|||
margin: auto; |
|||
background-size: contain; |
|||
animation: progressRotate 0.6s steps(6, start) infinite; |
|||
} |
|||
@keyframes progressRotate { |
|||
0% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
16% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png); |
|||
} |
|||
32% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png); |
|||
} |
|||
48% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png); |
|||
} |
|||
64% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png); |
|||
} |
|||
80% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png); |
|||
} |
|||
100% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
<!-- 下拉刷新区域 --> |
|||
<template> |
|||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view v-if="isDownLoading" class="downwarp-progress"></view> |
|||
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view> |
|||
<view class="downwarp-tip">{{ downText }}</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object, // down的配置项 |
|||
type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {}; |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.type === 3; |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)'; |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
switch (this.type) { |
|||
case 1: |
|||
return this.mOption.textInOffset; |
|||
case 2: |
|||
return this.mOption.textOutOffset; |
|||
case 3: |
|||
return this.mOption.textLoading; |
|||
case 4: |
|||
return this.mOption.textLoading; |
|||
default: |
|||
return this.mOption.textInOffset; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import '../../../mescroll-uni/components/mescroll-down.css'; |
|||
@import './mescroll-down.css'; |
|||
</style> |
@ -0,0 +1,32 @@ |
|||
/*上拉加载--旋转进度条*/ |
|||
.mescroll-upwarp .upwarp-progress { |
|||
width: 36px; |
|||
height: 36px; |
|||
border: none; |
|||
margin: auto; |
|||
background-size: contain; |
|||
animation: progressRotate 0.6s steps(6, start) infinite; |
|||
} |
|||
@keyframes progressRotate { |
|||
0% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
16% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png); |
|||
} |
|||
32% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png); |
|||
} |
|||
48% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png); |
|||
} |
|||
64% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png); |
|||
} |
|||
80% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png); |
|||
} |
|||
100% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<!-- 上拉加载区域 --> |
|||
<template> |
|||
<view class="mescroll-upwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="isUpLoading"> |
|||
<view class="upwarp-progress mescroll-rotate"></view> |
|||
<view class="upwarp-tip">{{ mOption.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object, // up的配置项 |
|||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {}; |
|||
}, |
|||
// 加载中 |
|||
isUpLoading() { |
|||
return this.type === 1; |
|||
}, |
|||
// 没有更多了 |
|||
isUpNoMore() { |
|||
return this.type === 2; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import '../../../mescroll-uni/components/mescroll-up.css'; |
|||
@import './mescroll-up.css'; |
|||
</style> |
@ -0,0 +1,380 @@ |
|||
<template> |
|||
<view |
|||
class="mescroll-body mescroll-render-touch" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" |
|||
:class="{'mescorll-sticky': sticky}" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp" |
|||
> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view v-if="isDownLoading" class="downwarp-progress"></view> |
|||
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view> |
|||
<view class="downwarp-tip">{{ downText }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
}; |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏 |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight(){ |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset() { |
|||
return this.downLoadType === 3 || this.downLoadType === 4; |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : ''; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.downLoadType === 3; |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)'; |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
if(!this.mescroll) return ""; |
|||
switch (this.downLoadType) { |
|||
case 1: |
|||
return this.mescroll.optDown.textInOffset; |
|||
case 2: |
|||
return this.mescroll.optDown.textOutOffset; |
|||
case 3: |
|||
return this.mescroll.optDown.textLoading; |
|||
case 4: |
|||
return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset; |
|||
default: |
|||
return this.mescroll.optDown.textInOffset; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num) { |
|||
if (typeof num === 'string') { |
|||
if (num.indexOf('px') !== -1) { |
|||
if (num.indexOf('rpx') !== -1) { |
|||
// "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if (num.indexOf('upx') !== -1) { |
|||
// "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { |
|||
// "10px" |
|||
return Number(num.replace('px', '')); |
|||
} |
|||
} else if (num.indexOf('%') !== -1) { |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace('%', '')) / 100; |
|||
return this.windowHeight * rate; |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0; |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll); |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
beforeEndDownScroll(mescroll){ |
|||
vm.downLoadType = 4; |
|||
return mescroll.optDown.beforeEndDelay // 延时结束的时长 |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时 |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|||
if(vm.downLoadType === 4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
if(typeof y === 'string'){ |
|||
// 滚动到指定view (y为css选择器) |
|||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let top = rect.top |
|||
top += vm.mescroll.getScrollTop() |
|||
uni.pageScrollTo({ |
|||
scrollTop: top, |
|||
duration: t |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
},30) |
|||
} else{ |
|||
// 滚动到指定位置 (y必须为数字) |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-uni/mescroll-uni.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
@import "./components/mescroll-up.css"; |
|||
</style> |
@ -0,0 +1,64 @@ |
|||
// 全局配置
|
|||
// mescroll-body 和 mescroll-uni 通用
|
|||
const GlobalOption = { |
|||
down: { |
|||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
}, |
|||
up: { |
|||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
|||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
|||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
}, |
|||
empty: { |
|||
use: true, // 是否显示空布局
|
|||
icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|||
} |
|||
}, |
|||
// 国际化配置
|
|||
i18n: { |
|||
// 中文
|
|||
zh: { |
|||
down: { |
|||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textSuccess: '加载成功', // 加载成功的文本
|
|||
textErr: '加载失败', // 加载失败的文本
|
|||
}, |
|||
up: { |
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
|||
empty: { |
|||
tip: '~ 空空如也 ~' // 空提示
|
|||
} |
|||
} |
|||
}, |
|||
// 英文
|
|||
en: { |
|||
down: { |
|||
textInOffset: 'drop down refresh', |
|||
textOutOffset: 'release updates', |
|||
textLoading: 'loading ...', |
|||
textSuccess: 'loaded successfully', |
|||
textErr: 'loading failed' |
|||
}, |
|||
up: { |
|||
textLoading: 'loading ...', |
|||
textNoMore: '-- END --', |
|||
empty: { |
|||
tip: '~ absolutely empty ~' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default GlobalOption |
@ -0,0 +1,462 @@ |
|||
<template> |
|||
<view class="mescroll-uni-warp"> |
|||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false"> |
|||
<view class="mescroll-uni-content mescroll-render-touch" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp"> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view v-if="isDownLoading" class="downwarp-progress"></view> |
|||
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view> |
|||
<view class="downwarp-tip">{{ downText }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素) |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
scrollTop: 0, // 滚动条的位置 |
|||
scrollAnim: false, // 是否开启滚动动画 |
|||
windowTop: 0, // 可使用窗口的顶部位置 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
} |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
disableScroll: Boolean // 是否禁止滚动 |
|||
}, |
|||
computed: { |
|||
// 是否使用fixed定位 (当height有值,则不使用) |
|||
isFixed(){ |
|||
return !this.height && this.fixed |
|||
}, |
|||
// mescroll的高度 |
|||
scrollHeight(){ |
|||
if (this.isFixed) { |
|||
return "auto" |
|||
} else if(this.height){ |
|||
return this.toPx(this.height) + 'px' |
|||
}else{ |
|||
return "100%" |
|||
} |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
fixedTop() { |
|||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0 |
|||
}, |
|||
padTop() { |
|||
return !this.isFixed ? this.numTop + 'px' : 0 |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom) |
|||
}, |
|||
fixedBottom() { |
|||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0 |
|||
}, |
|||
padBottom() { |
|||
return !this.isFixed ? this.numBottom + 'px' : 0 |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset(){ |
|||
return this.downLoadType===3 || this.downLoadType===4 |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : '' |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 列表是否可滑动 |
|||
scrollable(){ |
|||
if(this.disableScroll) return false |
|||
return this.downLoadType===0 || this.isDownReset |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.downLoadType === 3; |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)'; |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
if(!this.mescroll) return ""; |
|||
switch (this.downLoadType) { |
|||
case 1: |
|||
return this.mescroll.optDown.textInOffset; |
|||
case 2: |
|||
return this.mescroll.optDown.textOutOffset; |
|||
case 3: |
|||
return this.mescroll.optDown.textLoading; |
|||
case 4: |
|||
return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset; |
|||
default: |
|||
return this.mescroll.optDown.textInOffset; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num){ |
|||
if(typeof num === "string"){ |
|||
if (num.indexOf('px') !== -1) { |
|||
if(num.indexOf('rpx') !== -1) { // "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if(num.indexOf('upx') !== -1) { // "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { // "10px" |
|||
return Number(num.replace('px', '')) |
|||
} |
|||
}else if (num.indexOf('%') !== -1){ |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace("%","")) / 100 |
|||
return this.windowHeight * rate |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0 |
|||
}, |
|||
//注册列表滚动事件,用于下拉刷新和上拉加载 |
|||
scroll(e) { |
|||
this.mescroll.scroll(e.detail, () => { |
|||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动 |
|||
}) |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll) |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
}, |
|||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页) |
|||
setClientHeight() { |
|||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) { |
|||
this.isExec = true; // 避免多次获取 |
|||
this.$nextTick(() => { // 确保dom已渲染 |
|||
this.getClientInfo(data=>{ |
|||
this.isExec = false; |
|||
if (data) { |
|||
this.mescroll.setClientHeight(data.height); |
|||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次 |
|||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1; |
|||
setTimeout(() => { |
|||
this.setClientHeight() |
|||
}, this.clientNum * 100) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
}, |
|||
// 获取滚动区域的信息 |
|||
getClientInfo(success){ |
|||
let query = uni.createSelectorQuery(); |
|||
// #ifndef MP-ALIPAY || MP-DINGTALK |
|||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值 |
|||
// #endif |
|||
let view = query.select('#' + this.viewId); |
|||
view.boundingClientRect(data => { |
|||
success(data) |
|||
}).exec(); |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
beforeEndDownScroll(mescroll){ |
|||
vm.downLoadType = 4; |
|||
return mescroll.optDown.beforeEndDelay // 延时结束的时长 |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downResetTimer && clearTimeout(vm.downResetTimer) |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整 |
|||
if(vm.downLoadType===4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll) |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
// 更新容器的高度 (多mescroll的情况) |
|||
vm.setClientHeight() |
|||
} |
|||
} |
|||
} |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({ |
|||
'down': vm.down, |
|||
'up': vm.up |
|||
})) // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption); |
|||
vm.mescroll.viewId = vm.viewId; // 附带id |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if(sys.windowTop) vm.windowTop = sys.windowTop; |
|||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是scrollview,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡 |
|||
if(typeof y === 'string'){ |
|||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现 |
|||
vm.getClientInfo(function(rect){ |
|||
let mescrollTop = rect.top // mescroll到顶部的距离 |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let curY = vm.mescroll.getScrollTop() |
|||
let top = rect.top - mescrollTop |
|||
top += curY |
|||
if(!vm.isFixed) top -= vm.numTop |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = top |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
}) |
|||
return; |
|||
} |
|||
let curY = vm.mescroll.getScrollTop() |
|||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡 |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = y |
|||
}) |
|||
} else { |
|||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t |
|||
vm.scrollTop = step |
|||
}, t) |
|||
} |
|||
}) |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
mounted() { |
|||
// 设置容器的高度 |
|||
this.setClientHeight() |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-uni/mescroll-uni.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
@import "./components/mescroll-up.css"; |
|||
</style> |
@ -0,0 +1,116 @@ |
|||
<!--空布局: |
|||
遵循easycom规范, 可作为独立的组件, 不使用mescroll的页面也能使用: |
|||
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty> |
|||
--> |
|||
<template> |
|||
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }"> |
|||
<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view> |
|||
<view v-if="tip" class="empty-tip">{{ tip }}</view> |
|||
<view v-if="btnText" class="empty-btn" @click="emptyClick">{{ btnText }}</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 引入全局配置 |
|||
import GlobalOption from '../mescroll-uni/mescroll-uni-option.js'; |
|||
// 引入国际化工具类 |
|||
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js'; |
|||
export default { |
|||
props: { |
|||
// empty的配置项: 默认为GlobalOption.up.empty |
|||
option: { |
|||
type: Object, |
|||
default() { |
|||
return {}; |
|||
} |
|||
} |
|||
}, |
|||
// 使用computed获取配置,用于支持option的动态配置 |
|||
computed: { |
|||
// 图标 |
|||
icon() { |
|||
if (this.option.icon != null) { // 此处不使用短路求值, 用于支持传空串不显示图标 |
|||
return this.option.icon |
|||
} else{ |
|||
let i18nType = mescrollI18n.getType() // 国际化配置 |
|||
if (this.option.i18n) { |
|||
return this.option.i18n[i18nType].icon |
|||
} else{ |
|||
return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon |
|||
} |
|||
} |
|||
}, |
|||
// 文本提示 |
|||
tip() { |
|||
if (this.option.tip != null) { // 支持传空串不显示文本提示 |
|||
return this.option.tip |
|||
} else{ |
|||
let i18nType = mescrollI18n.getType() // 国际化配置 |
|||
if (this.option.i18n) { |
|||
return this.option.i18n[i18nType].tip |
|||
} else{ |
|||
return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip |
|||
} |
|||
} |
|||
}, |
|||
// 按钮文本 |
|||
btnText() { |
|||
if (this.option.i18n) { |
|||
let i18nType = mescrollI18n.getType() // 国际化配置 |
|||
return this.option.i18n[i18nType].btnText |
|||
} else{ |
|||
return this.option.btnText |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
// 点击按钮 |
|||
emptyClick() { |
|||
this.$emit('emptyclick'); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
/* 无任何数据的空布局 */ |
|||
.mescroll-empty { |
|||
box-sizing: border-box; |
|||
width: 100%; |
|||
padding: 100rpx 50rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.mescroll-empty.empty-fixed { |
|||
z-index: 99; |
|||
position: absolute; /*transform会使fixed失效,最终会降级为absolute */ |
|||
top: 100rpx; |
|||
left: 0; |
|||
} |
|||
|
|||
.mescroll-empty .empty-icon { |
|||
width: 280rpx; |
|||
height: 280rpx; |
|||
} |
|||
|
|||
.mescroll-empty .empty-tip { |
|||
margin-top: 20rpx; |
|||
font-size: 24rpx; |
|||
color: gray; |
|||
} |
|||
|
|||
.mescroll-empty .empty-btn { |
|||
display: inline-block; |
|||
margin-top: 40rpx; |
|||
min-width: 200rpx; |
|||
padding: 18rpx; |
|||
font-size: 28rpx; |
|||
border: 1rpx solid #e04b28; |
|||
border-radius: 60rpx; |
|||
color: #e04b28; |
|||
} |
|||
|
|||
.mescroll-empty .empty-btn:active { |
|||
opacity: 0.75; |
|||
} |
|||
</style> |
@ -0,0 +1,55 @@ |
|||
/* 下拉刷新区域 */ |
|||
.mescroll-downwarp { |
|||
position: absolute; |
|||
top: -100%; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
text-align: center; |
|||
} |
|||
|
|||
/* 下拉刷新--内容区,定位于区域底部 */ |
|||
.mescroll-downwarp .downwarp-content { |
|||
position: absolute; |
|||
left: 0; |
|||
bottom: 0; |
|||
width: 100%; |
|||
min-height: 60rpx; |
|||
padding: 20rpx 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
/* 下拉刷新--提示文本 */ |
|||
.mescroll-downwarp .downwarp-tip { |
|||
display: inline-block; |
|||
font-size: 28rpx; |
|||
vertical-align: middle; |
|||
margin-left: 16rpx; |
|||
/* color: gray; 已在style设置color,此处删去*/ |
|||
} |
|||
|
|||
/* 下拉刷新--旋转进度条 */ |
|||
.mescroll-downwarp .downwarp-progress { |
|||
display: inline-block; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid gray; |
|||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
/* 旋转动画 */ |
|||
.mescroll-downwarp .mescroll-rotate { |
|||
animation: mescrollDownRotate 0.6s linear infinite; |
|||
} |
|||
|
|||
@keyframes mescrollDownRotate { |
|||
0% { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
<!-- 下拉刷新区域 --> |
|||
<template> |
|||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view> |
|||
<view class="downwarp-tip">{{downText}}</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object , // down的配置项 |
|||
type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption(){ |
|||
return this.option || {} |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.type === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return 'rotate(' + 360 * this.rate + 'deg)' |
|||
}, |
|||
// 文本提示 |
|||
downText(){ |
|||
switch (this.type){ |
|||
case 1: return this.mOption.textInOffset; |
|||
case 2: return this.mOption.textOutOffset; |
|||
case 3: return this.mOption.textLoading; |
|||
case 4: return this.mOption.textLoading; |
|||
default: return this.mOption.textInOffset; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "./mescroll-down.css"; |
|||
</style> |
@ -0,0 +1,83 @@ |
|||
<!-- 回到顶部的按钮 --> |
|||
<template> |
|||
<image |
|||
v-if="mOption.src" |
|||
class="mescroll-totop" |
|||
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]" |
|||
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}" |
|||
:src="mOption.src" |
|||
mode="widthFix" |
|||
@click="toTopClick" |
|||
/> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
// up.toTop的配置项 |
|||
option: Object, |
|||
// 是否显示 |
|||
value: false |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption(){ |
|||
return this.option || {} |
|||
}, |
|||
// 优先显示左边 |
|||
left(){ |
|||
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto'; |
|||
}, |
|||
// 右边距离 (优先显示左边) |
|||
right() { |
|||
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right); |
|||
} |
|||
}, |
|||
methods: { |
|||
addUnit(num){ |
|||
if(!num) return 0; |
|||
if(typeof num === 'number') return num + 'rpx'; |
|||
return num |
|||
}, |
|||
toTopClick() { |
|||
this.$emit('input', false); // 使v-model生效 |
|||
this.$emit('click'); // 派发点击事件 |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
/* 回到顶部的按钮 */ |
|||
.mescroll-totop { |
|||
z-index: 9990; |
|||
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */ |
|||
right: 20rpx; |
|||
bottom: 120rpx; |
|||
width: 72rpx; |
|||
height: auto; |
|||
border-radius: 50%; |
|||
opacity: 0; |
|||
transition: opacity 0.5s; /* 过渡 */ |
|||
margin-bottom: var(--window-bottom); /* css变量 */ |
|||
} |
|||
|
|||
/* 适配 iPhoneX */ |
|||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|||
.mescroll-totop-safearea { |
|||
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */ |
|||
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom)); |
|||
} |
|||
} |
|||
|
|||
/* 显示 -- 淡入 */ |
|||
.mescroll-totop-in { |
|||
opacity: 1; |
|||
} |
|||
|
|||
/* 隐藏 -- 淡出且不接收事件*/ |
|||
.mescroll-totop-out { |
|||
opacity: 0; |
|||
pointer-events: none; |
|||
} |
|||
</style> |
@ -0,0 +1,47 @@ |
|||
/* 上拉加载区域 */ |
|||
.mescroll-upwarp { |
|||
box-sizing: border-box; |
|||
min-height: 110rpx; |
|||
padding: 30rpx 0; |
|||
text-align: center; |
|||
clear: both; |
|||
} |
|||
|
|||
/*提示文本 */ |
|||
.mescroll-upwarp .upwarp-tip, |
|||
.mescroll-upwarp .upwarp-nodata { |
|||
display: inline-block; |
|||
font-size: 28rpx; |
|||
vertical-align: middle; |
|||
/* color: gray; 已在style设置color,此处删去*/ |
|||
} |
|||
|
|||
.mescroll-upwarp .upwarp-tip { |
|||
margin-left: 16rpx; |
|||
} |
|||
|
|||
/*旋转进度条 */ |
|||
.mescroll-upwarp .upwarp-progress { |
|||
display: inline-block; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid gray; |
|||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
/* 旋转动画 */ |
|||
.mescroll-upwarp .mescroll-rotate { |
|||
animation: mescrollUpRotate 0.6s linear infinite; |
|||
} |
|||
|
|||
@keyframes mescrollUpRotate { |
|||
0% { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
<!-- 上拉加载区域 --> |
|||
<template> |
|||
<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="isUpLoading"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mOption.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object, // up的配置项 |
|||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {}; |
|||
}, |
|||
// 加载中 |
|||
isUpLoading() { |
|||
return this.type === 1; |
|||
}, |
|||
// 没有更多了 |
|||
isUpNoMore() { |
|||
return this.type === 2; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import './mescroll-up.css'; |
|||
</style> |
@ -0,0 +1,15 @@ |
|||
// 国际化工具类
|
|||
const mescrollI18n = { |
|||
// 默认语言
|
|||
def: "zh", |
|||
// 获取当前语言类型
|
|||
getType(){ |
|||
return uni.getStorageSync("mescroll-i18n") || this.def |
|||
}, |
|||
// 设置当前语言类型
|
|||
setType(type){ |
|||
uni.setStorageSync("mescroll-i18n", type) |
|||
} |
|||
} |
|||
|
|||
export default mescrollI18n |
@ -0,0 +1,57 @@ |
|||
// mescroll-body 和 mescroll-uni 通用
|
|||
const MescrollMixin = { |
|||
data() { |
|||
return { |
|||
mescroll: null //mescroll实例对象
|
|||
} |
|||
}, |
|||
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
onPullDownRefresh(){ |
|||
this.mescroll && this.mescroll.onPullDownRefresh(); |
|||
}, |
|||
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|||
onPageScroll(e) { |
|||
this.mescroll && this.mescroll.onPageScroll(e); |
|||
}, |
|||
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|||
onReachBottom() { |
|||
this.mescroll && this.mescroll.onReachBottom(); |
|||
}, |
|||
methods: { |
|||
// mescroll组件初始化的回调,可获取到mescroll对象
|
|||
mescrollInit(mescroll) { |
|||
this.mescroll = mescroll; |
|||
this.mescrollInitByRef(); // 兼容字节跳动小程序
|
|||
}, |
|||
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
|
|||
mescrollInitByRef() { |
|||
if(!this.mescroll || !this.mescroll.resetUpScroll){ |
|||
let mescrollRef = this.$refs.mescrollRef; |
|||
if(mescrollRef) this.mescroll = mescrollRef.mescroll |
|||
} |
|||
}, |
|||
// 下拉刷新的回调 (mixin默认resetUpScroll)
|
|||
downCallback() { |
|||
if(this.mescroll.optUp.use){ |
|||
this.mescroll.resetUpScroll() |
|||
}else{ |
|||
setTimeout(()=>{ |
|||
this.mescroll.endSuccess(); |
|||
}, 500) |
|||
} |
|||
}, |
|||
// 上拉加载的回调
|
|||
upCallback() { |
|||
// mixin默认延时500自动结束加载
|
|||
setTimeout(()=>{ |
|||
this.mescroll.endErr(); |
|||
}, 500) |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
|
|||
} |
|||
|
|||
} |
|||
|
|||
export default MescrollMixin; |
@ -0,0 +1,64 @@ |
|||
// 全局配置
|
|||
// mescroll-body 和 mescroll-uni 通用
|
|||
const GlobalOption = { |
|||
down: { |
|||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
}, |
|||
up: { |
|||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
|||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
|||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
}, |
|||
empty: { |
|||
use: true, // 是否显示空布局
|
|||
icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|||
} |
|||
}, |
|||
// 国际化配置
|
|||
i18n: { |
|||
// 中文
|
|||
zh: { |
|||
down: { |
|||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textSuccess: '加载成功', // 加载成功的文本
|
|||
textErr: '加载失败', // 加载失败的文本
|
|||
}, |
|||
up: { |
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
|||
empty: { |
|||
tip: '~ 空空如也 ~' // 空提示
|
|||
} |
|||
} |
|||
}, |
|||
// 英文
|
|||
en: { |
|||
down: { |
|||
textInOffset: 'drop down refresh', |
|||
textOutOffset: 'release updates', |
|||
textLoading: 'loading ...', |
|||
textSuccess: 'loaded successfully', |
|||
textErr: 'loading failed' |
|||
}, |
|||
up: { |
|||
textLoading: 'loading ...', |
|||
textNoMore: '-- END --', |
|||
empty: { |
|||
tip: '~ absolutely empty ~' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default GlobalOption |
@ -0,0 +1,36 @@ |
|||
.mescroll-uni-warp{ |
|||
height: 100%; |
|||
} |
|||
|
|||
.mescroll-uni-content{ |
|||
height: 100%; |
|||
} |
|||
|
|||
.mescroll-uni { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 100%; |
|||
min-height: 200rpx; |
|||
overflow-y: auto; |
|||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ |
|||
} |
|||
|
|||
/* 定位的方式固定高度 */ |
|||
.mescroll-uni-fixed{ |
|||
z-index: 1; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: auto; /* 使right生效 */ |
|||
height: auto; /* 使bottom生效 */ |
|||
} |
|||
|
|||
/* 适配 iPhoneX */ |
|||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|||
.mescroll-safearea { |
|||
padding-bottom: constant(safe-area-inset-bottom); |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
} |
|||
} |
@ -0,0 +1,799 @@ |
|||
/* mescroll |
|||
* version 1.3.7 |
|||
* 2021-04-12 wenju |
|||
* https://www.mescroll.com
|
|||
*/ |
|||
|
|||
export default function MeScroll(options, isScrollBody) { |
|||
let me = this; |
|||
me.version = '1.3.7'; // mescroll版本号
|
|||
me.options = options || {}; // 配置
|
|||
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
|
|||
|
|||
me.isDownScrolling = false; // 是否在执行下拉刷新的回调
|
|||
me.isUpScrolling = false; // 是否在执行上拉加载的回调
|
|||
let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
|
|||
|
|||
// 初始化下拉刷新
|
|||
me.initDownScroll(); |
|||
// 初始化上拉加载,则初始化
|
|||
me.initUpScroll(); |
|||
|
|||
// 自动加载
|
|||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|||
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
|
|||
if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) { |
|||
if (me.optDown.autoShowLoading) { |
|||
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
|
|||
} else { |
|||
me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
|
|||
} |
|||
} |
|||
// 自动触发上拉加载
|
|||
if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
|
|||
setTimeout(function(){ |
|||
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll(); |
|||
},100) |
|||
} |
|||
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
|
|||
} |
|||
|
|||
/* 配置参数:下拉刷新 */ |
|||
MeScroll.prototype.extendDownScroll = function(optDown) { |
|||
// 下拉刷新的配置
|
|||
MeScroll.extend(optDown, { |
|||
use: true, // 是否启用下拉刷新; 默认true
|
|||
auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
|
|||
native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
|
|||
isLock: false, // 是否锁定下拉刷新,默认false;
|
|||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|||
startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
|
|||
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
|||
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
|||
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
|
|||
minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
|
|||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textSuccess: '加载成功', // 加载成功的文本
|
|||
textErr: '加载失败', // 加载失败的文本
|
|||
beforeEndDelay: 0, // 延时结束的时长 (显示加载成功/失败的时长, android小程序设置此项结束下拉会卡顿, 配置后请注意测试)
|
|||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
|
|||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
|||
inited: null, // 下拉刷新初始化完毕的回调
|
|||
inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
|
|||
outOffset: null, // 下拉的距离大于offset那一刻的回调
|
|||
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
|
|||
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
|
|||
showLoading: null, // 显示下拉刷新进度的回调
|
|||
afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
|
|||
beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
|
|||
endDownScroll: null, // 结束下拉刷新的回调
|
|||
afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
|
|||
callback: function(mescroll) { |
|||
// 下拉刷新的回调;默认重置上拉加载列表为第一页
|
|||
mescroll.resetUpScroll(); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/* 配置参数:上拉加载 */ |
|||
MeScroll.prototype.extendUpScroll = function(optUp) { |
|||
// 上拉加载的配置
|
|||
MeScroll.extend(optUp, { |
|||
use: true, // 是否启用上拉加载; 默认true
|
|||
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
|
|||
isLock: false, // 是否锁定上拉加载,默认false;
|
|||
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
|
|||
callback: null, // 上拉加载的回调;function(page,mescroll){ }
|
|||
page: { |
|||
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
|
|||
size: 10, // 每页数据的数量
|
|||
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
|
|||
}, |
|||
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
|
|||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
|||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
|
|||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
|||
inited: null, // 初始化完毕的回调
|
|||
showLoading: null, // 显示加载中的回调
|
|||
showNoMore: null, // 显示无更多数据的回调
|
|||
hideUpScroll: null, // 隐藏上拉加载的回调
|
|||
errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: null, // 图片路径,默认null (绝对路径或网络图)
|
|||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
|
|||
duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
|
|||
btnClick: null, // 点击按钮的回调
|
|||
onShow: null, // 是否显示的回调
|
|||
zIndex: 9990, // fixed定位z-index值
|
|||
left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
|
|||
width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
}, |
|||
empty: { |
|||
use: true, // 是否显示空布局
|
|||
icon: null, // 图标路径
|
|||
tip: '~ 暂无相关数据 ~', // 提示
|
|||
btnText: '', // 按钮
|
|||
btnClick: null, // 点击按钮的回调
|
|||
onShow: null, // 是否显示的回调
|
|||
fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
|
|||
top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
|
|||
zIndex: 99 // fixed定位z-index值
|
|||
}, |
|||
onScroll: false // 是否监听滚动事件
|
|||
}) |
|||
} |
|||
|
|||
/* 配置参数 */ |
|||
MeScroll.extend = function(userOption, defaultOption) { |
|||
if (!userOption) return defaultOption; |
|||
for (let key in defaultOption) { |
|||
if (userOption[key] == null) { |
|||
let def = defaultOption[key]; |
|||
if (def != null && typeof def === 'object') { |
|||
userOption[key] = MeScroll.extend({}, def); // 深度匹配
|
|||
} else { |
|||
userOption[key] = def; |
|||
} |
|||
} else if (typeof userOption[key] === 'object') { |
|||
MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
|
|||
} |
|||
} |
|||
return userOption; |
|||
} |
|||
|
|||
/* 简单判断是否配置了颜色 (非透明,非白色) */ |
|||
MeScroll.prototype.hasColor = function(color) { |
|||
if(!color) return false; |
|||
let c = color.toLowerCase(); |
|||
return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white" |
|||
} |
|||
|
|||
/* -------初始化下拉刷新------- */ |
|||
MeScroll.prototype.initDownScroll = function() { |
|||
let me = this; |
|||
// 配置参数
|
|||
me.optDown = me.options.down || {}; |
|||
if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
|||
me.extendDownScroll(me.optDown); |
|||
|
|||
// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
|
|||
if(me.isScrollBody && me.optDown.native){ |
|||
me.optDown.use = false |
|||
}else{ |
|||
me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
|
|||
} |
|||
|
|||
me.downHight = 0; // 下拉区域的高度
|
|||
|
|||
// 在页面中加入下拉布局
|
|||
if (me.optDown.use && me.optDown.inited) { |
|||
// 初始化完毕的回调
|
|||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|||
me.optDown.inited(me); |
|||
}, 0) |
|||
} |
|||
} |
|||
|
|||
/* 列表touchstart事件 */ |
|||
MeScroll.prototype.touchstartEvent = function(e) { |
|||
if (!this.optDown.use) return; |
|||
|
|||
this.startPoint = this.getPoint(e); // 记录起点
|
|||
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
|
|||
this.startAngle = 0; // 初始角度
|
|||
this.lastPoint = this.startPoint; // 重置上次move的点
|
|||
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
|
|||
this.inTouchend = false; // 标记不是touchend
|
|||
} |
|||
|
|||
/* 列表touchmove事件 */ |
|||
MeScroll.prototype.touchmoveEvent = function(e) { |
|||
if (!this.optDown.use) return; |
|||
let me = this; |
|||
|
|||
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
|
|||
let curPoint = me.getPoint(e); // 当前点
|
|||
|
|||
let 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.optUp.isBoth))) { |
|||
|
|||
// 下拉的初始角度是否在配置的范围内
|
|||
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
|
|||
if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
|
|||
|
|||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
|
|||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) { |
|||
me.inTouchend = true; // 标记执行touchend
|
|||
me.touchendEvent(); // 提前触发touchend
|
|||
return; |
|||
} |
|||
|
|||
me.preventDefault(e); // 阻止默认事件
|
|||
|
|||
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
|
|||
|
|||
// 下拉距离 < 指定距离
|
|||
if (me.downHight < me.optDown.offset) { |
|||
if (me.movetype !== 1) { |
|||
me.movetype = 1; // 加入标记,保证只执行一次
|
|||
me.isDownEndSuccess = null; // 重置是否加载成功的状态 (wxs执行的是wxs.wxs)
|
|||
me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
|
|||
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.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|||
} |
|||
if (diff > 0) { // 向下拉
|
|||
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
|
|||
} else { // 向上收
|
|||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
|
|||
} |
|||
} |
|||
|
|||
me.downHight = Math.round(me.downHight) // 取整
|
|||
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
|
|||
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
|
|||
} |
|||
} |
|||
|
|||
me.lastPoint = curPoint; // 记录本次移动的点
|
|||
} |
|||
|
|||
/* 列表touchend事件 */ |
|||
MeScroll.prototype.touchendEvent = function(e) { |
|||
if (!this.optDown.use) return; |
|||
// 如果下拉区域高度已改变,则需重置回来
|
|||
if (this.isMoveDown) { |
|||
if (this.downHight >= this.optDown.offset) { |
|||
// 符合触发刷新的条件
|
|||
this.triggerDownScroll(); |
|||
} else { |
|||
// 不符合的话 则重置
|
|||
this.downHight = 0; |
|||
this.endDownScrollCall(this); |
|||
} |
|||
this.movetype = 0; |
|||
this.isMoveDown = false; |
|||
} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
|
|||
let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|||
// 上滑
|
|||
if (isScrollUp) { |
|||
// 需检查滑动的角度
|
|||
let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
|
|||
if (angle > 80) { |
|||
// 检查并触发上拉
|
|||
this.triggerUpScroll(true); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 根据点击滑动事件获取第一个手指的坐标 */ |
|||
MeScroll.prototype.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]*/ |
|||
MeScroll.prototype.getAngle = function(p1, p2) { |
|||
let x = Math.abs(p1.x - p2.x); |
|||
let y = Math.abs(p1.y - p2.y); |
|||
let z = Math.sqrt(x * x + y * y); |
|||
let angle = 0; |
|||
if (z !== 0) { |
|||
angle = Math.asin(y / z) / Math.PI * 180; |
|||
} |
|||
return angle |
|||
} |
|||
|
|||
/* 触发下拉刷新 */ |
|||
MeScroll.prototype.triggerDownScroll = function() { |
|||
if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) { |
|||
//return true则处于完全自定义状态
|
|||
} else { |
|||
this.showDownScroll(); // 下拉刷新中...
|
|||
!this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
|||
} |
|||
} |
|||
|
|||
/* 显示下拉进度布局 */ |
|||
MeScroll.prototype.showDownScroll = function() { |
|||
this.isDownScrolling = true; // 标记下拉中
|
|||
if (this.optDown.native) { |
|||
uni.startPullDownRefresh(); // 系统自带的下拉刷新
|
|||
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
|
|||
} else{ |
|||
this.downHight = this.optDown.offset; // 更新下拉区域高度
|
|||
this.showDownLoadingCall(this.downHight); // 下拉刷新中...
|
|||
} |
|||
} |
|||
|
|||
MeScroll.prototype.showDownLoadingCall = function(downHight) { |
|||
this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
|
|||
this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
|
|||
} |
|||
|
|||
/* 显示系统自带的下拉刷新时需要处理的业务 */ |
|||
MeScroll.prototype.onPullDownRefresh = function() { |
|||
this.isDownScrolling = true; // 标记下拉中
|
|||
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
|
|||
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
|||
} |
|||
|
|||
/* 结束下拉刷新 */ |
|||
MeScroll.prototype.endDownScroll = function() { |
|||
if (this.optDown.native) { // 结束原生下拉刷新
|
|||
this.isDownScrolling = false; |
|||
this.endDownScrollCall(this); |
|||
uni.stopPullDownRefresh(); |
|||
return |
|||
} |
|||
let me = this; |
|||
// 结束下拉刷新的方法
|
|||
let endScroll = function() { |
|||
me.downHight = 0; |
|||
me.isDownScrolling = false; |
|||
me.endDownScrollCall(me); |
|||
if(!me.isScrollBody){ |
|||
me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
|
|||
me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
|
|||
} |
|||
} |
|||
// 结束下拉刷新时的回调
|
|||
let delay = 0; |
|||
if (me.optDown.beforeEndDownScroll) { |
|||
delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
|
|||
if(me.isDownEndSuccess == null) delay = 0; // 没有执行加载中,则不延时
|
|||
} |
|||
if (typeof delay === 'number' && delay > 0) { |
|||
setTimeout(endScroll, delay); |
|||
} else { |
|||
endScroll(); |
|||
} |
|||
} |
|||
|
|||
MeScroll.prototype.endDownScrollCall = function() { |
|||
this.optDown.endDownScroll && this.optDown.endDownScroll(this); |
|||
this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this); |
|||
} |
|||
|
|||
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */ |
|||
MeScroll.prototype.lockDownScroll = function(isLock) { |
|||
if (isLock == null) isLock = true; |
|||
this.optDown.isLock = isLock; |
|||
} |
|||
|
|||
/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */ |
|||
MeScroll.prototype.lockUpScroll = function(isLock) { |
|||
if (isLock == null) isLock = true; |
|||
this.optUp.isLock = isLock; |
|||
} |
|||
|
|||
/* -------初始化上拉加载------- */ |
|||
MeScroll.prototype.initUpScroll = function() { |
|||
let me = this; |
|||
// 配置参数
|
|||
me.optUp = me.options.up || {use: false} |
|||
if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
|||
me.extendUpScroll(me.optUp); |
|||
|
|||
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
|
|||
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
|
|||
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
|
|||
|
|||
// 初始化完毕的回调
|
|||
if (me.optUp.inited) { |
|||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|||
me.optUp.inited(me); |
|||
}, 0) |
|||
} |
|||
} |
|||
|
|||
/*滚动到底部的事件 (仅mescroll-body生效)*/ |
|||
MeScroll.prototype.onReachBottom = function() { |
|||
if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
|
|||
if (!this.optUp.isLock && this.optUp.hasNext) { |
|||
this.triggerUpScroll(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/*列表滚动事件 (仅mescroll-body生效)*/ |
|||
MeScroll.prototype.onPageScroll = function(e) { |
|||
if (!this.isScrollBody) return; |
|||
|
|||
// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
|
|||
this.setScrollTop(e.scrollTop); |
|||
|
|||
// 顶部按钮的显示隐藏
|
|||
if (e.scrollTop >= this.optUp.toTop.offset) { |
|||
this.showTopBtn(); |
|||
} else { |
|||
this.hideTopBtn(); |
|||
} |
|||
} |
|||
|
|||
/*列表滚动事件*/ |
|||
MeScroll.prototype.scroll = function(e, onScroll) { |
|||
// 更新滚动条的位置
|
|||
this.setScrollTop(e.scrollTop); |
|||
// 更新滚动内容高度
|
|||
this.setScrollHeight(e.scrollHeight); |
|||
|
|||
// 向上滑还是向下滑动
|
|||
if (this.preScrollY == null) this.preScrollY = 0; |
|||
this.isScrollUp = e.scrollTop - this.preScrollY > 0; |
|||
this.preScrollY = e.scrollTop; |
|||
|
|||
// 上滑 && 检查并触发上拉
|
|||
this.isScrollUp && this.triggerUpScroll(true); |
|||
|
|||
// 顶部按钮的显示隐藏
|
|||
if (e.scrollTop >= this.optUp.toTop.offset) { |
|||
this.showTopBtn(); |
|||
} else { |
|||
this.hideTopBtn(); |
|||
} |
|||
|
|||
// 滑动监听
|
|||
this.optUp.onScroll && onScroll && onScroll() |
|||
} |
|||
|
|||
/* 触发上拉加载 */ |
|||
MeScroll.prototype.triggerUpScroll = function(isCheck) { |
|||
if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) { |
|||
// 是否校验在底部; 默认不校验
|
|||
if (isCheck === true) { |
|||
let canUp = false; |
|||
// 还有下一页 && 没有锁定 && 不在下拉中
|
|||
if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) { |
|||
if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
|
|||
canUp = true; // 标记可上拉
|
|||
} |
|||
} |
|||
if (canUp === false) return; |
|||
} |
|||
this.showUpScroll(); // 上拉加载中...
|
|||
this.optUp.page.num++; // 预先加一页,如果失败则减回
|
|||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
|||
this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
|||
this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.optUp.callback(this); // 执行回调,联网加载数据
|
|||
} |
|||
} |
|||
|
|||
/* 显示上拉加载中 */ |
|||
MeScroll.prototype.showUpScroll = function() { |
|||
this.isUpScrolling = true; // 标记上拉加载中
|
|||
this.optUp.showLoading && this.optUp.showLoading(this); // 回调
|
|||
} |
|||
|
|||
/* 显示上拉无更多数据 */ |
|||
MeScroll.prototype.showNoMore = function() { |
|||
this.optUp.hasNext = false; // 标记无更多数据
|
|||
this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
|
|||
} |
|||
|
|||
/* 隐藏上拉区域**/ |
|||
MeScroll.prototype.hideUpScroll = function() { |
|||
this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
|
|||
} |
|||
|
|||
/* 结束上拉加载 */ |
|||
MeScroll.prototype.endUpScroll = function(isShowNoMore) { |
|||
if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
|
|||
if (isShowNoMore) { |
|||
this.showNoMore(); // isShowNoMore=true,显示无更多数据
|
|||
} else { |
|||
this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
|
|||
} |
|||
} |
|||
this.isUpScrolling = false; // 标记结束上拉加载
|
|||
} |
|||
|
|||
/* 重置上拉加载列表为第一页 |
|||
*isShowLoading 是否显示进度布局; |
|||
* 1.默认null,不传参,则显示上拉加载的进度布局 |
|||
* 2.传参true, 则显示下拉刷新的进度布局 |
|||
* 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据) |
|||
*/ |
|||
MeScroll.prototype.resetUpScroll = function(isShowLoading) { |
|||
if (this.optUp && this.optUp.use) { |
|||
let page = this.optUp.page; |
|||
this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
|
|||
this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
|
|||
page.num = this.startNum; // 重置为第一页
|
|||
page.time = null; // 重置时间为空
|
|||
if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
|
|||
if (isShowLoading == null) { |
|||
this.removeEmpty(); // 移除空布局
|
|||
this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
|
|||
} else { |
|||
this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
|
|||
} |
|||
} |
|||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
|||
this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
|||
this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
|
|||
} |
|||
} |
|||
|
|||
/* 设置page.num的值 */ |
|||
MeScroll.prototype.setPageNum = function(num) { |
|||
this.optUp.page.num = num - 1; |
|||
} |
|||
|
|||
/* 设置page.size的值 */ |
|||
MeScroll.prototype.setPageSize = function(size) { |
|||
this.optUp.page.size = size; |
|||
} |
|||
|
|||
/* 联网回调成功,结束下拉刷新和上拉加载 |
|||
* dataSize: 当前页的数据量(必传) |
|||
* totalPage: 总页数(必传) |
|||
* systime: 服务器时间 (可空) |
|||
*/ |
|||
MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) { |
|||
let hasNext; |
|||
if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
|
|||
this.endSuccess(dataSize, hasNext, systime); |
|||
} |
|||
|
|||
/* 联网回调成功,结束下拉刷新和上拉加载 |
|||
* dataSize: 当前页的数据量(必传) |
|||
* totalSize: 列表所有数据总数量(必传) |
|||
* systime: 服务器时间 (可空) |
|||
*/ |
|||
MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) { |
|||
let hasNext; |
|||
if (this.optUp.use && totalSize != null) { |
|||
let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
|
|||
hasNext = loadSize < totalSize; // 是否还有下一页
|
|||
} |
|||
this.endSuccess(dataSize, hasNext, systime); |
|||
} |
|||
|
|||
/* 联网回调成功,结束下拉刷新和上拉加载 |
|||
* dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页 |
|||
* hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据. |
|||
* systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录 |
|||
*/ |
|||
MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) { |
|||
let me = this; |
|||
// 结束下拉刷新
|
|||
if (me.isDownScrolling) { |
|||
me.isDownEndSuccess = true |
|||
me.endDownScroll(); |
|||
} |
|||
|
|||
// 结束上拉加载
|
|||
if (me.optUp.use) { |
|||
let isShowNoMore; // 是否已无更多数据
|
|||
if (dataSize != null) { |
|||
let pageNum = me.optUp.page.num; // 当前页码
|
|||
let pageSize = me.optUp.page.size; // 每页长度
|
|||
// 如果是第一页
|
|||
if (pageNum === 1) { |
|||
if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
|
|||
} |
|||
if (dataSize < pageSize || hasNext === false) { |
|||
// 返回的数据不满一页时,则说明已无更多数据
|
|||
me.optUp.hasNext = false; |
|||
if (dataSize === 0 && pageNum === 1) { |
|||
// 如果第一页无任何数据且配置了空布局
|
|||
isShowNoMore = false; |
|||
me.showEmpty(); |
|||
} else { |
|||
// 总列表数少于配置的数量,则不显示无更多数据
|
|||
let allDataSize = (pageNum - 1) * pageSize + dataSize; |
|||
if (allDataSize < me.optUp.noMoreSize) { |
|||
isShowNoMore = false; |
|||
} else { |
|||
isShowNoMore = true; |
|||
} |
|||
me.removeEmpty(); // 移除空布局
|
|||
} |
|||
} else { |
|||
// 还有下一页
|
|||
isShowNoMore = false; |
|||
me.optUp.hasNext = true; |
|||
me.removeEmpty(); // 移除空布局
|
|||
} |
|||
} |
|||
|
|||
// 隐藏上拉
|
|||
me.endUpScroll(isShowNoMore); |
|||
} |
|||
} |
|||
|
|||
/* 回调失败,结束下拉刷新和上拉加载 */ |
|||
MeScroll.prototype.endErr = function(errDistance) { |
|||
// 结束下拉,回调失败重置回原来的页码和时间
|
|||
if (this.isDownScrolling) { |
|||
this.isDownEndSuccess = false |
|||
let page = this.optUp.page; |
|||
if (page && this.prePageNum) { |
|||
page.num = this.prePageNum; |
|||
page.time = this.prePageTime; |
|||
} |
|||
this.endDownScroll(); |
|||
} |
|||
// 结束上拉,回调失败重置回原来的页码
|
|||
if (this.isUpScrolling) { |
|||
this.optUp.page.num--; |
|||
this.endUpScroll(false); |
|||
// 如果是mescroll-body,则需往回滚一定距离
|
|||
if(this.isScrollBody && errDistance !== 0){ // 不处理0
|
|||
if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
|
|||
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
|
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 显示空布局 */ |
|||
MeScroll.prototype.showEmpty = function() { |
|||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true) |
|||
} |
|||
|
|||
/* 移除空布局 */ |
|||
MeScroll.prototype.removeEmpty = function() { |
|||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false) |
|||
} |
|||
|
|||
/* 显示回到顶部的按钮 */ |
|||
MeScroll.prototype.showTopBtn = function() { |
|||
if (!this.topBtnShow) { |
|||
this.topBtnShow = true; |
|||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true); |
|||
} |
|||
} |
|||
|
|||
/* 隐藏回到顶部的按钮 */ |
|||
MeScroll.prototype.hideTopBtn = function() { |
|||
if (this.topBtnShow) { |
|||
this.topBtnShow = false; |
|||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false); |
|||
} |
|||
} |
|||
|
|||
/* 获取滚动条的位置 */ |
|||
MeScroll.prototype.getScrollTop = function() { |
|||
return this.scrollTop || 0 |
|||
} |
|||
|
|||
/* 记录滚动条的位置 */ |
|||
MeScroll.prototype.setScrollTop = function(y) { |
|||
this.scrollTop = y; |
|||
} |
|||
|
|||
/* 滚动到指定位置 */ |
|||
MeScroll.prototype.scrollTo = function(y, t) { |
|||
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
|
|||
} |
|||
|
|||
/* 自定义scrollTo */ |
|||
MeScroll.prototype.resetScrollTo = function(myScrollTo) { |
|||
this.myScrollTo = myScrollTo |
|||
} |
|||
|
|||
/* 滚动条到底部的距离 */ |
|||
MeScroll.prototype.getScrollBottom = function() { |
|||
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop() |
|||
} |
|||
|
|||
/* 计步器 |
|||
star: 开始值 |
|||
end: 结束值 |
|||
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器; |
|||
t: 计步时长,传0则直接回调end值;不传则默认300ms |
|||
rate: 周期;不传则默认30ms计步一次 |
|||
* */ |
|||
MeScroll.prototype.getStep = function(star, end, callback, t, rate) { |
|||
let diff = end - star; // 差值
|
|||
if (t === 0 || diff === 0) { |
|||
callback && callback(end); |
|||
return; |
|||
} |
|||
t = t || 300; // 时长 300ms
|
|||
rate = rate || 30; // 周期 30ms
|
|||
let count = t / rate; // 次数
|
|||
let step = diff / count; // 步长
|
|||
let i = 0; // 计数
|
|||
let timer = setInterval(function() { |
|||
if (i < count - 1) { |
|||
star += step; |
|||
callback && callback(star, timer); |
|||
i++; |
|||
} else { |
|||
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
|
|||
clearInterval(timer); |
|||
} |
|||
}, rate); |
|||
} |
|||
|
|||
/* 滚动容器的高度 */ |
|||
MeScroll.prototype.getClientHeight = function(isReal) { |
|||
let h = this.clientHeight || 0 |
|||
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
|
|||
h = this.getBodyHeight() |
|||
} |
|||
return h |
|||
} |
|||
MeScroll.prototype.setClientHeight = function(h) { |
|||
this.clientHeight = h; |
|||
} |
|||
|
|||
/* 滚动内容的高度 */ |
|||
MeScroll.prototype.getScrollHeight = function() { |
|||
return this.scrollHeight || 0; |
|||
} |
|||
MeScroll.prototype.setScrollHeight = function(h) { |
|||
this.scrollHeight = h; |
|||
} |
|||
|
|||
/* body的高度 */ |
|||
MeScroll.prototype.getBodyHeight = function() { |
|||
return this.bodyHeight || 0; |
|||
} |
|||
MeScroll.prototype.setBodyHeight = function(h) { |
|||
this.bodyHeight = h; |
|||
} |
|||
|
|||
/* 阻止浏览器默认滚动事件 */ |
|||
MeScroll.prototype.preventDefault = function(e) { |
|||
// 小程序不支持e.preventDefault, 已在wxs中禁止
|
|||
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
|
|||
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
|
|||
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault() |
|||
} |
@ -0,0 +1,477 @@ |
|||
<template> |
|||
<view class="mescroll-uni-warp"> |
|||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false"> |
|||
<view class="mescroll-uni-content mescroll-render-touch" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp"> |
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view> |
|||
<view class="downwarp-tip">{{downText}}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from './wxs/renderjs.js'; |
|||
export default { |
|||
mixins:[renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
// 引入mescroll-uni.js,处理核心逻辑 |
|||
import MeScroll from './mescroll-uni.js'; |
|||
// 引入全局配置 |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
// 引入国际化工具类 |
|||
import mescrollI18n from './mescroll-i18n.js'; |
|||
// 引入回到顶部组件 |
|||
import MescrollTop from './components/mescroll-top.vue'; |
|||
// 引入兼容wxs(含renderjs)写法的mixins |
|||
import WxsMixin from './wxs/mixins.js'; |
|||
|
|||
/** |
|||
* mescroll-uni 嵌在页面某个区域的下拉刷新和上拉加载组件, 如嵌在弹窗,浮层,swiper中... |
|||
* @property {Object} down 下拉刷新的参数配置 |
|||
* @property {Object} up 上拉加载的参数配置 |
|||
* @property {Object} i18n 国际化的参数配置 |
|||
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true |
|||
* @property {String, Number} height 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效) |
|||
* @property {Boolean} disableScroll 是否禁止滚动, 默认false |
|||
* @event {Function} init 初始化完成的回调 |
|||
* @event {Function} down 下拉刷新的回调 |
|||
* @event {Function} up 上拉加载的回调 |
|||
* @event {Function} emptyclick 点击empty配置的btnText按钮回调 |
|||
* @event {Function} topclick 点击回到顶部的按钮回调 |
|||
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效) |
|||
* @example <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-uni> |
|||
*/ |
|||
export default { |
|||
name: 'mescroll-uni', |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
props: { |
|||
down: Object, |
|||
up: Object, |
|||
i18n: Object, |
|||
top: [String, Number], |
|||
topbar: [Boolean, String], |
|||
bottom: [String, Number], |
|||
safearea: Boolean, |
|||
fixed: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
height: [String, Number], |
|||
bottombar:{ |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
disableScroll: Boolean |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: {optDown:{},optUp:{}}, // mescroll实例 |
|||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素) |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
scrollTop: 0, // 滚动条的位置 |
|||
scrollAnim: false, // 是否开启滚动动画 |
|||
windowTop: 0, // 可使用窗口的顶部位置 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
} |
|||
}, |
|||
computed: { |
|||
// 是否使用fixed定位 (当height有值,则不使用) |
|||
isFixed(){ |
|||
return !this.height && this.fixed |
|||
}, |
|||
// mescroll的高度 |
|||
scrollHeight(){ |
|||
if (this.isFixed) { |
|||
return "auto" |
|||
} else if(this.height){ |
|||
return this.toPx(this.height) + 'px' |
|||
}else{ |
|||
return "100%" |
|||
} |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
fixedTop() { |
|||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0 |
|||
}, |
|||
padTop() { |
|||
return !this.isFixed ? this.numTop + 'px' : 0 |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom) |
|||
}, |
|||
fixedBottom() { |
|||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0 |
|||
}, |
|||
padBottom() { |
|||
return !this.isFixed ? this.numBottom + 'px' : 0 |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset(){ |
|||
return this.downLoadType===3 || this.downLoadType===4 |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : ''; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 列表是否可滑动 |
|||
scrollable(){ |
|||
if(this.disableScroll) return false |
|||
return this.downLoadType===0 || this.isDownReset |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return 'rotate(' + 360 * this.downRate + 'deg)' |
|||
}, |
|||
// 文本提示 |
|||
downText(){ |
|||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错 |
|||
switch (this.downLoadType){ |
|||
case 1: return this.mescroll.optDown.textInOffset; |
|||
case 2: return this.mescroll.optDown.textOutOffset; |
|||
case 3: return this.mescroll.optDown.textLoading; |
|||
case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset; |
|||
default: return this.mescroll.optDown.textInOffset; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num){ |
|||
if(typeof num === "string"){ |
|||
if (num.indexOf('px') !== -1) { |
|||
if(num.indexOf('rpx') !== -1) { // "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if(num.indexOf('upx') !== -1) { // "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { // "10px" |
|||
return Number(num.replace('px', '')) |
|||
} |
|||
}else if (num.indexOf('%') !== -1){ |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace("%","")) / 100 |
|||
return this.windowHeight * rate |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0 |
|||
}, |
|||
//注册列表滚动事件,用于下拉刷新和上拉加载 |
|||
scroll(e) { |
|||
this.mescroll.scroll(e.detail, () => { |
|||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动 |
|||
}) |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll) |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
}, |
|||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页) |
|||
setClientHeight() { |
|||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) { |
|||
this.isExec = true; // 避免多次获取 |
|||
this.$nextTick(() => { // 确保dom已渲染 |
|||
this.getClientInfo(data=>{ |
|||
this.isExec = false; |
|||
if (data) { |
|||
this.mescroll.setClientHeight(data.height); |
|||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次 |
|||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1; |
|||
setTimeout(() => { |
|||
this.setClientHeight() |
|||
}, this.clientNum * 100) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
}, |
|||
// 获取滚动区域的信息 |
|||
getClientInfo(success){ |
|||
let query = uni.createSelectorQuery(); |
|||
// #ifndef MP-ALIPAY || MP-DINGTALK |
|||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值 |
|||
// #endif |
|||
let view = query.select('#' + this.viewId); |
|||
view.boundingClientRect(data => { |
|||
success(data) |
|||
}).exec(); |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
beforeEndDownScroll(mescroll){ |
|||
vm.downLoadType = 4; |
|||
return mescroll.optDown.beforeEndDelay // 延时结束的时长 |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downResetTimer && clearTimeout(vm.downResetTimer) |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整 |
|||
if(vm.downLoadType===4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll) |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
// 更新容器的高度 (多mescroll的情况) |
|||
vm.setClientHeight() |
|||
} |
|||
} |
|||
} |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption); |
|||
vm.mescroll.viewId = vm.viewId; // 附带id |
|||
vm.mescroll.i18n = i18nOption; // 挂载语言包 |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if(sys.windowTop) vm.windowTop = sys.windowTop; |
|||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是scrollview,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡 |
|||
if(typeof y === 'string'){ |
|||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现 |
|||
vm.getClientInfo(function(rect){ |
|||
let mescrollTop = rect.top // mescroll到顶部的距离 |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let curY = vm.mescroll.getScrollTop() |
|||
let top = rect.top - mescrollTop |
|||
top += curY |
|||
if(!vm.isFixed) top -= vm.numTop |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = top |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
}) |
|||
return; |
|||
} |
|||
let curY = vm.mescroll.getScrollTop() |
|||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡 |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = y |
|||
}) |
|||
} else { |
|||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t |
|||
vm.scrollTop = step |
|||
}, t) |
|||
} |
|||
}) |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
mounted() { |
|||
// 设置容器的高度 |
|||
this.setClientHeight() |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
@import "./mescroll-uni.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
@import './components/mescroll-up.css'; |
|||
</style> |
@ -0,0 +1,47 @@ |
|||
/** |
|||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期 |
|||
*/ |
|||
const MescrollCompMixin = { |
|||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
|
|||
onPageScroll(e) { |
|||
this.handlePageScroll(e) |
|||
}, |
|||
onReachBottom() { |
|||
this.handleReachBottom() |
|||
}, |
|||
// 当down的native: true时, 还需传递此方法进到子组件
|
|||
onPullDownRefresh(){ |
|||
this.handlePullDownRefresh() |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
|
|||
onPageScroll: e=>{ |
|||
this.handlePageScroll(e) |
|||
}, |
|||
onReachBottom: ()=>{ |
|||
this.handleReachBottom() |
|||
}, |
|||
onPullDownRefresh: ()=>{ |
|||
this.handlePullDownRefresh() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
methods:{ |
|||
handlePageScroll(e){ |
|||
let item = this.$refs["mescrollItem"]; |
|||
if(item && item.mescroll) item.mescroll.onPageScroll(e); |
|||
}, |
|||
handleReachBottom(){ |
|||
let item = this.$refs["mescrollItem"]; |
|||
if(item && item.mescroll) item.mescroll.onReachBottom(); |
|||
}, |
|||
handlePullDownRefresh(){ |
|||
let item = this.$refs["mescrollItem"]; |
|||
if(item && item.mescroll) item.mescroll.onPullDownRefresh(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default MescrollCompMixin; |
@ -0,0 +1,66 @@ |
|||
/** |
|||
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例) |
|||
*/ |
|||
const MescrollMoreItemMixin = { |
|||
// 支付宝小程序不支持props的mixin,需写在具体的页面中
|
|||
// #ifndef MP-ALIPAY || MP-DINGTALK
|
|||
props:{ |
|||
i: Number, // 每个tab页的专属下标
|
|||
index: { // 当前tab的下标
|
|||
type: Number, |
|||
default(){ |
|||
return 0 |
|||
} |
|||
} |
|||
}, |
|||
// #endif
|
|||
data() { |
|||
return { |
|||
downOption:{ |
|||
auto:false // 不自动加载
|
|||
}, |
|||
upOption:{ |
|||
auto:false // 不自动加载
|
|||
}, |
|||
isInit: false // 当前tab是否已初始化
|
|||
} |
|||
}, |
|||
watch:{ |
|||
// 监听下标的变化
|
|||
index(val){ |
|||
if (this.i === val && !this.isInit) this.mescrollTrigger() |
|||
} |
|||
}, |
|||
methods: { |
|||
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
|
|||
mescrollInitByRef() { |
|||
if(!this.mescroll || !this.mescroll.resetUpScroll){ |
|||
// 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标'
|
|||
let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i]; |
|||
if(mescrollRef) this.mescroll = mescrollRef.mescroll |
|||
} |
|||
}, |
|||
// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
|
|||
mescrollInit(mescroll) { |
|||
this.mescroll = mescroll; |
|||
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
|
|||
// 自动加载当前tab的数据
|
|||
if(this.i === this.index){ |
|||
this.mescrollTrigger() |
|||
} |
|||
}, |
|||
// 主动触发加载
|
|||
mescrollTrigger(){ |
|||
this.isInit = true; // 标记为true
|
|||
if (this.mescroll) { |
|||
if (this.mescroll.optDown.use) { |
|||
this.mescroll.triggerDownScroll(); |
|||
} else{ |
|||
this.mescroll.triggerUpScroll(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default MescrollMoreItemMixin; |
@ -0,0 +1,74 @@ |
|||
/** |
|||
* mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期 |
|||
*/ |
|||
const MescrollMoreMixin = { |
|||
data() { |
|||
return { |
|||
tabIndex: 0, // 当前tab下标
|
|||
mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
|
|||
onPageScroll: e=>{ |
|||
this.handlePageScroll(e) |
|||
}, |
|||
onReachBottom: ()=>{ |
|||
this.handleReachBottom() |
|||
}, |
|||
onPullDownRefresh: ()=>{ |
|||
this.handlePullDownRefresh() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|||
onPageScroll(e) { |
|||
this.handlePageScroll(e) |
|||
}, |
|||
onReachBottom() { |
|||
this.handleReachBottom() |
|||
}, |
|||
// 当down的native: true时, 还需传递此方法进到子组件
|
|||
onPullDownRefresh(){ |
|||
this.handlePullDownRefresh() |
|||
}, |
|||
methods:{ |
|||
handlePageScroll(e){ |
|||
let mescroll = this.getMescroll(this.tabIndex); |
|||
mescroll && mescroll.onPageScroll(e); |
|||
}, |
|||
handleReachBottom(){ |
|||
let mescroll = this.getMescroll(this.tabIndex); |
|||
mescroll && mescroll.onReachBottom(); |
|||
}, |
|||
handlePullDownRefresh(){ |
|||
let mescroll = this.getMescroll(this.tabIndex); |
|||
mescroll && mescroll.onPullDownRefresh(); |
|||
}, |
|||
// 根据下标获取对应子组件的mescroll
|
|||
getMescroll(i){ |
|||
if(!this.mescrollItems) this.mescrollItems = []; |
|||
if(!this.mescrollItems[i]) { |
|||
// v-for中的refs
|
|||
let vForItem = this.$refs["mescrollItem"]; |
|||
if(vForItem){ |
|||
this.mescrollItems[i] = vForItem[i] |
|||
}else{ |
|||
// 普通的refs,不可重复
|
|||
this.mescrollItems[i] = this.$refs["mescrollItem"+i]; |
|||
} |
|||
} |
|||
let item = this.mescrollItems[i] |
|||
return item ? item.mescroll : null |
|||
}, |
|||
// 切换tab,恢复滚动条位置
|
|||
tabChange(i){ |
|||
let mescroll = this.getMescroll(i); |
|||
if(mescroll){ |
|||
// 延时(比$nextTick靠谱一些),确保元素已渲染
|
|||
setTimeout(()=>{ |
|||
mescroll.scrollTo(mescroll.getScrollTop(),0) |
|||
},30) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default MescrollMoreMixin; |
@ -0,0 +1,109 @@ |
|||
// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信
|
|||
const WxsMixin = { |
|||
data() { |
|||
return { |
|||
// 传入wxs视图层的数据 (响应式)
|
|||
wxsProp: { |
|||
optDown:{}, // 下拉刷新的配置
|
|||
scrollTop:0, // 滚动条的距离
|
|||
bodyHeight:0, // body的高度
|
|||
isDownScrolling:false, // 是否正在下拉刷新中
|
|||
isUpScrolling:false, // 是否正在上拉加载中
|
|||
isScrollBody:true, // 是否为mescroll-body滚动
|
|||
isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新
|
|||
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
|
|||
}, |
|||
|
|||
// 标记调用wxs视图层的方法
|
|||
callProp: { |
|||
callType: '', // 方法名
|
|||
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
|
|||
}, |
|||
|
|||
// 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs)
|
|||
// #ifndef MP-WEIXIN || MP-QQ || APP-PLUS || H5
|
|||
wxsBiz: { |
|||
//注册列表touchstart事件,用于下拉刷新
|
|||
touchstartEvent: e=> { |
|||
this.mescroll.touchstartEvent(e); |
|||
}, |
|||
//注册列表touchmove事件,用于下拉刷新
|
|||
touchmoveEvent: e=> { |
|||
this.mescroll.touchmoveEvent(e); |
|||
}, |
|||
//注册列表touchend事件,用于下拉刷新
|
|||
touchendEvent: e=> { |
|||
this.mescroll.touchendEvent(e); |
|||
}, |
|||
propObserver(){}, // 抹平wxs的写法
|
|||
callObserver(){} // 抹平wxs的写法
|
|||
}, |
|||
// #endif
|
|||
|
|||
// 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js)
|
|||
// #ifndef APP-PLUS || H5
|
|||
renderBiz: { |
|||
propObserver(){} // 抹平renderjs的写法
|
|||
} |
|||
// #endif
|
|||
} |
|||
}, |
|||
methods: { |
|||
// wxs视图层调用逻辑层的回调
|
|||
wxsCall(msg){ |
|||
if(msg.type === 'setWxsProp'){ |
|||
// 更新wxsProp数据 (值改变才触发更新)
|
|||
this.wxsProp = { |
|||
optDown: this.mescroll.optDown, |
|||
scrollTop: this.mescroll.getScrollTop(), |
|||
bodyHeight: this.mescroll.getBodyHeight(), |
|||
isDownScrolling: this.mescroll.isDownScrolling, |
|||
isUpScrolling: this.mescroll.isUpScrolling, |
|||
isUpBoth: this.mescroll.optUp.isBoth, |
|||
isScrollBody:this.mescroll.isScrollBody, |
|||
t: Date.now() |
|||
} |
|||
}else if(msg.type === 'setLoadType'){ |
|||
// 设置inOffset,outOffset的状态
|
|||
this.downLoadType = msg.downLoadType |
|||
// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
|
|||
this.$set(this.mescroll, 'downLoadType', this.downLoadType) |
|||
// 重置是否加载成功的状态
|
|||
this.$set(this.mescroll, 'isDownEndSuccess', null) |
|||
}else if(msg.type === 'triggerDownScroll'){ |
|||
// 主动触发下拉刷新
|
|||
this.mescroll.triggerDownScroll(); |
|||
}else if(msg.type === 'endDownScroll'){ |
|||
// 结束下拉刷新
|
|||
this.mescroll.endDownScroll(); |
|||
}else if(msg.type === 'triggerUpScroll'){ |
|||
// 主动触发上拉加载
|
|||
this.mescroll.triggerUpScroll(true); |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5
|
|||
// 配置主动触发wxs显示加载进度的回调
|
|||
this.mescroll.optDown.afterLoading = ()=>{ |
|||
this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|||
} |
|||
// 配置主动触发wxs隐藏加载进度的回调
|
|||
this.mescroll.optDown.afterEndDownScroll = ()=>{ |
|||
this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|||
let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0) |
|||
setTimeout(()=>{ |
|||
if(this.downLoadType === 4 || this.downLoadType === 0){ |
|||
this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|||
} |
|||
// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
|
|||
this.$set(this.mescroll, 'downLoadType', this.downLoadType) |
|||
}, delay) |
|||
} |
|||
// 初始化wxs的数据
|
|||
this.wxsCall({type: 'setWxsProp'}) |
|||
// #endif
|
|||
} |
|||
} |
|||
|
|||
export default WxsMixin; |
@ -0,0 +1,92 @@ |
|||
// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
|
|||
// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
|
|||
// https://uniapp.dcloud.io/frame?id=renderjs
|
|||
|
|||
// 与wxs的me实例一致
|
|||
var me = {} |
|||
|
|||
// 初始化window对象的touch事件 (仅初始化一次)
|
|||
if(window && !window.$mescrollRenderInit){ |
|||
window.$mescrollRenderInit = true |
|||
|
|||
|
|||
window.addEventListener('touchstart', function(e){ |
|||
if (me.disabled()) return; |
|||
me.startPoint = me.getPoint(e); // 记录起点
|
|||
}, {passive: true}) |
|||
|
|||
|
|||
window.addEventListener('touchmove', function(e){ |
|||
if (me.disabled()) return; |
|||
if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
|
|||
|
|||
var curPoint = me.getPoint(e); // 当前点
|
|||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|||
// 向下拉
|
|||
if (moveY > 0) { |
|||
// 可下拉的条件
|
|||
if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) { |
|||
|
|||
// 只有touch在mescroll的view上面,才禁止bounce
|
|||
var el = e.target; |
|||
var isMescrollTouch = false; |
|||
while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") { |
|||
var cls = el.classList; |
|||
if (cls && cls.contains('mescroll-render-touch')) { |
|||
isMescrollTouch = true |
|||
break; |
|||
} |
|||
el = el.parentNode; // 继续检查其父元素
|
|||
} |
|||
// 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
|
|||
if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault(); |
|||
} |
|||
} |
|||
}, {passive: false}) |
|||
} |
|||
|
|||
/* 获取滚动条的位置 */ |
|||
me.getScrollTop = function() { |
|||
return me.scrollTop || 0 |
|||
} |
|||
|
|||
/* 是否禁用下拉刷新 */ |
|||
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} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 监听逻辑层数据的变化 (实时更新数据) |
|||
*/ |
|||
function propObserver(wxsProp) { |
|||
me.optDown = wxsProp.optDown |
|||
me.scrollTop = wxsProp.scrollTop |
|||
me.isDownScrolling = wxsProp.isDownScrolling |
|||
me.isUpScrolling = wxsProp.isUpScrolling |
|||
me.isUpBoth = wxsProp.isUpBoth |
|||
} |
|||
|
|||
/* 导出模块 */ |
|||
const renderBiz = { |
|||
data() { |
|||
return { |
|||
propObserver: propObserver, |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default renderBiz; |
@ -0,0 +1,268 @@ |
|||
// 使用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 |
|||
} |
@ -0,0 +1,80 @@ |
|||
{ |
|||
"id": "mescroll-uni", |
|||
"displayName": "【wxs+renderjs实现】高性能的下拉刷新上拉加载组件", |
|||
"version": "1.3.7", |
|||
"description": "支持uni-app的下拉刷新和上拉加载的组件,支持原生页面和局部区域滚动,支持国际化", |
|||
"keywords": [ |
|||
"mescroll", |
|||
"下拉刷新", |
|||
"上拉加载", |
|||
"翻页", |
|||
"分页" |
|||
], |
|||
"repository": "https://github.com/mescroll/mescroll", |
|||
"engines": { |
|||
"HBuilderX": "^3.1.0" |
|||
}, |
|||
"dcloudext": { |
|||
"category": [ |
|||
"前端组件", |
|||
"通用组件" |
|||
], |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "无", |
|||
"data": "无", |
|||
"permissions": "无" |
|||
}, |
|||
"npmurl": "https://www.npmjs.com/package/mescroll-uni" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": [], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "y", |
|||
"aliyun": "y" |
|||
}, |
|||
"client": { |
|||
"App": { |
|||
"app-vue": "y", |
|||
"app-nvue": "n" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "y", |
|||
"Android Browser": "y", |
|||
"微信浏览器(Android)": "y", |
|||
"QQ浏览器(Android)": "y" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "y", |
|||
"IE": "y", |
|||
"Edge": "y", |
|||
"Firefox": "y", |
|||
"Safari": "y" |
|||
}, |
|||
"小程序": { |
|||
"微信": "y", |
|||
"阿里": "y", |
|||
"百度": "y", |
|||
"字节跳动": "y", |
|||
"QQ": "y" |
|||
}, |
|||
"快应用": { |
|||
"华为": "y", |
|||
"联盟": "y" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件 |
|||
1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件 |
|||
|
|||
2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 .. |
|||
|
|||
3. mescroll的uni版本 丰富的案例, 自由灵活的api, 超详细的注释, 可让您快速自定义真正属于自己的下拉上拉组件 |
|||
|
|||
<br/> |
|||
|
|||
|
|||
## 最新文档(1.3.7版本): <a href="https://www.mescroll.com/uni.html">https://www.mescroll.com/uni.html</a> |
|||
2021-04-13 by 小瑾同学 (文档可能会有缓存,建议打开时刷新一下) |
|||
|
|||
|
|||
## 1.3.5版本已调整为[uni_modules](https://uniapp.dcloud.io/uni_modules) |
|||
uni_modules版本的mescroll-body 和 mescroll-empty 支持 [easycom规范](https://uniapp.dcloud.io/collocation/pages?id=easycom) |
|||
所以 main.js 无需再为mescroll-body注册全局组件 |
|||
所以个别页面要单独使用 mescroll-empty , 也无需手动注册 |
|||
#### 1.3.5以前的用户升级为uni_modules版本: |
|||
``` |
|||
1. 删除原来的 @/components/mescroll-uni 组件 |
|||
2. 删除 main.js 注册的 mescroll 组件 |
|||
3. 从插件市场导入最新mescroll组件 (1.3.5+uni_modules版本) |
|||
4. 全局搜索 '@/components/mescroll-uni/' 替换为 '@/uni_modules/mescroll-uni/components/mescroll-uni/' |
|||
5. mescroll-empty遵循easycom规范, 若某些页面单独使用 'mescroll-empty.vue', 可删除手动导入的代码 |
|||
``` |
|||
|
|||
## 近期已更新优化的内容: |
|||
1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题 |
|||
2. 新增`入门极简`示例, 国际化`mescroll-i18n.vue`示例, 轮播吸顶菜单`mescroll-swiper-sticky.vue`示例 |
|||
3. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue |
|||
4. 新增 me-video 视频组件, 解决APP端视频下拉悬浮错位的问题, 参考 mescroll-options.vue 示例 |
|||
5. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动 |
|||
6. 吸顶悬浮提供了原生sticky和监听滚动条实现的示例: sticky.vue 和 sticky-scroll.vue (推荐使用sticky样式实现) |
|||
7. mescroll.scrollTo(y)的y支持css选择器, 包括跨自定义组件的后代选择器, 支持滚动到子组件的view (参考 mescroll-options.vue) |
|||
8. topbar 顶部是否预留状态栏的高度, 默认false; 还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)' |
|||
9. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)' |
|||
10. topbar,bgColor支持一行代码定义background: [https://www.runoob.com/cssref/css3-pr-background.html](https://www.runoob.com/cssref/css3-pr-background.html) |
|||
<br/> |
|||
<br/> |
|||
<a href="https://ext.dcloud.net.cn/plugin?id=343&update_log">查看更多 ... </a> |
|||
|
|||
<br/> |
|||
|
|||
#### mescroll不支持nvue,也暂无支持的计划哈,so sorry~ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue