402 lines
11 KiB
Vue
402 lines
11 KiB
Vue
<template>
|
|
<view class="tn-scroll-view-class tn-scroll-view">
|
|
<scroll-view
|
|
class="scroll-view"
|
|
:style="[scrollViewStyle]"
|
|
scroll-y
|
|
scroll-anchoring
|
|
enable-back-to-top
|
|
:throttle="false"
|
|
:scroll-top="scrollTop"
|
|
:lower-threshold="lowerThreshold"
|
|
@scroll="handleScroll"
|
|
@touchend="handleTouchEnd"
|
|
@touchmove.prevent.stop="handleTouchMove"
|
|
@touchstart="handleTouchStart"
|
|
@scrolltolower="handleScrollTolower"
|
|
>
|
|
<view class="scroll__content" :style="[scrollContentStyle]">
|
|
<view class="scroll__pull-down">
|
|
<slot name="pulldown">
|
|
<view class="scroll__refresh" :style="[refreshStyle]">
|
|
<view><tn-loading :animation="refreshing"></tn-loading></view>
|
|
<view class="scroll__refresh--text" :style="[refreshTextStyle]">{{ refreshStateText }}</view>
|
|
</view>
|
|
</slot>
|
|
</view>
|
|
<view :id="elScrollDataId" class="scroll__data">
|
|
<slot></slot>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import componentsColor from '../../libs/mixin/components_color.js'
|
|
export default {
|
|
name: 'tn-scroll-view',
|
|
mixins: [ componentsColor ],
|
|
props: {
|
|
// H5顶部导航栏的高度
|
|
h5NavHeight: {
|
|
type: Number,
|
|
default: 45
|
|
},
|
|
// 自定义顶部导航栏高度
|
|
customNavHeight: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
// 可滚动区域顶部偏移高度
|
|
offsetTop: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
// 可滚动区域底部偏移高度
|
|
offsetBottom: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
// 容器高度 (不设置则自动计算)
|
|
height: {
|
|
type: Number,
|
|
default: null
|
|
},
|
|
// 是否禁用
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 禁用下拉刷新
|
|
pullDownDisabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 下拉速率
|
|
pullDownSpeed: {
|
|
type: Number,
|
|
default: 0.5
|
|
},
|
|
// 刷新延迟
|
|
refreshDelayed: {
|
|
type: Number,
|
|
default: 800
|
|
},
|
|
// 刷新完成后延迟
|
|
refreshFinishDelayed: {
|
|
type: Number,
|
|
default: 800
|
|
},
|
|
// 下拉刷新距离
|
|
refresherThreshold: {
|
|
type: Number,
|
|
default: 70
|
|
},
|
|
// 上拉加载距离
|
|
lowerThreshold: {
|
|
type: Number,
|
|
default: 40
|
|
},
|
|
// 刷新状态
|
|
refreshState: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 正在刷新文字
|
|
refreshingText: {
|
|
type: String,
|
|
default: '正在刷新'
|
|
},
|
|
// 刷新成功文字
|
|
refreshSuccessText: {
|
|
type: String,
|
|
default: '刷新成功'
|
|
},
|
|
// 下拉中的文字
|
|
pulldownText: {
|
|
type: String,
|
|
default: '下拉刷新'
|
|
},
|
|
// 下拉完成的文字
|
|
pulldownFinishText: {
|
|
type: String,
|
|
default: '松开刷新'
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
// 滚动容器内容id
|
|
elScrollDataId: '',
|
|
// 系统信息
|
|
systemInfo: {
|
|
height: 0,
|
|
statusBarHeight: 0
|
|
},
|
|
// 距离顶部滚动高度
|
|
scrollTop: 0,
|
|
// 滚动内容视图顶部位置
|
|
scrollDataTop: -1,
|
|
// 滚动内容视图顶部位置偏移
|
|
scrollDataOffsetTop: -1,
|
|
// 滚动区域的高度
|
|
scrollViewHeight: 0,
|
|
// 当前滚动高度
|
|
currentScrollTop: 0,
|
|
// 当前触摸点Y轴开始坐标
|
|
currentTouchStartY: 0,
|
|
// 刷新状态文字
|
|
refreshStateText: '下拉刷新',
|
|
// 是否刷新中
|
|
refreshing: false,
|
|
// 是否刷新完成
|
|
refreshFinish: false,
|
|
// 是否正在下拉
|
|
pulldowning: false,
|
|
// 下拉高度
|
|
pullDownHeight: 0,
|
|
// 是否显示下拉加载
|
|
showPullDown: false
|
|
}
|
|
},
|
|
computed: {
|
|
scrollViewStyle() {
|
|
let style = {}
|
|
style.height = this.scrollViewHeight + 'px'
|
|
if (!this.backgroundColorClass) {
|
|
style.backgroundColor = this.backgroundColorStyle
|
|
}
|
|
return style
|
|
},
|
|
scrollContentStyle() {
|
|
let style = {}
|
|
style.transform = this.showPullDown ? `translateY(${this.pullDownHeight}px)` : `translateY(0px)`
|
|
style.transition = this.pulldowning ? `transform 100ms ease-out` : `transform 500ms cubic-bezier(0.19,1.64,0.42,0.72)`
|
|
return style
|
|
},
|
|
refreshStyle() {
|
|
let style = {}
|
|
style.opacity = this.showPullDown ? 1 : 0
|
|
return style
|
|
},
|
|
refreshTextStyle() {
|
|
let style = {}
|
|
if (!this.fontColorClass) {
|
|
style.color = this.fontColorStyle
|
|
}
|
|
return style
|
|
},
|
|
loadTextStyle() {
|
|
let style = {}
|
|
if (!this.fontColorClass) {
|
|
style.color = this.fontColorStyle
|
|
}
|
|
return style
|
|
}
|
|
},
|
|
watch: {
|
|
refreshState(nVal, oVal) {
|
|
if (!nVal) {
|
|
if (this.showPullDown) {
|
|
// 关闭正在下拉
|
|
this.pulldowning = false
|
|
// 隐藏下拉刷新
|
|
this.showPullDown = false
|
|
// 关闭正在刷新
|
|
this.refreshing = false
|
|
}
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
this.elScrollDataId = this.$tn.uuid()
|
|
this.getSystemInfo()
|
|
},
|
|
mounted() {
|
|
this.$nextTick(() => {
|
|
this.init()
|
|
})
|
|
},
|
|
methods: {
|
|
// 组件初始化
|
|
init() {
|
|
this.refreshStateText = this.pulldownText
|
|
// 初始化scrollView信息
|
|
this.updateScrollViewInfo()
|
|
},
|
|
// 获取系统信息
|
|
getSystemInfo() {
|
|
const systemInfo = uni.getSystemInfoSync()
|
|
this.systemInfo.height = systemInfo.safeArea.height
|
|
this.systemInfo.statusBarHeight = systemInfo.statusBarHeight
|
|
},
|
|
// 更新scrollView信息
|
|
updateScrollViewInfo() {
|
|
if (this.height) {
|
|
this.scrollViewHeight = this.height
|
|
} else {
|
|
// 设置scrollView的高度和组件顶部位置
|
|
// console.log(this.systemInfo, this.offsetTop, this.customNavHeight);
|
|
// #ifdef H5
|
|
this.scrollViewHeight = this.systemInfo.height - (
|
|
this.offsetTop +
|
|
(this.customNavHeight ? this.customNavHeight : this.h5NavHeight) +
|
|
this.offsetBottom)
|
|
this.scrollDataOffsetTop = this.offsetTop + (this.customNavHeight ? this.customNavHeight : this.h5NavHeight)
|
|
// #endif
|
|
// #ifndef H5
|
|
this.scrollViewHeight = this.systemInfo.height - (
|
|
this.offsetTop +
|
|
this.systemInfo.statusBarHeight +
|
|
this.offsetBottom)
|
|
this.scrollDataOffsetTop = this.offsetTop + this.systemInfo.statusBarHeight
|
|
// #endif
|
|
}
|
|
},
|
|
// 获取scrollView内容信息
|
|
async getScrollDataInfo() {
|
|
const scrollInfo = await this._tGetRect(`#${this.elScrollDataId}`)
|
|
this.scrollDataTop = scrollInfo.top
|
|
},
|
|
// 上拉触底事件
|
|
handleScrollTolower(e) {
|
|
if (this.pullUpDisabled) return
|
|
this.$emit('scrolltolower', e)
|
|
},
|
|
// 滚动事件
|
|
handleScroll(e) {
|
|
this.currentScrollTop = e.detail.scrollTop
|
|
this.$emit('scroll', e.detail)
|
|
},
|
|
// 触摸按下事件
|
|
handleTouchStart(e) {
|
|
if (this.disabled) return
|
|
this.currentTouchStartY = e.touches[0].clientY
|
|
this.getScrollDataInfo()
|
|
this.$emit('touchStart', e)
|
|
},
|
|
// 触摸下滑事件
|
|
handleTouchMove(e) {
|
|
if (this.disabled) return
|
|
if (this.currentScrollTop == 0 && e.touches[0].clientY >= this.currentTouchStartY) {
|
|
// 容器滑动的偏移
|
|
const moveOffset = this.scrollDataTop > 0 ?
|
|
(this.scrollDataOffsetTop - this.scrollDataTop) :
|
|
(Math.abs(this.scrollDataTop) + this.scrollDataOffsetTop)
|
|
|
|
this.pulldowning = true
|
|
this.showPullDown = true
|
|
let pullDownDistance = ((e.touches[0].clientY - this.currentTouchStartY) - moveOffset) * this.pullDownSpeed
|
|
this.pullDownHeight = pullDownDistance
|
|
// this.pullDownHeight = pullDownDistance > this.refresherThreshold ? this.refresherThreshold : pullDownDistance
|
|
this.refreshStateText = this.pullDownHeight >= this.refresherThreshold ? this.pulldownFinishText : this.pulldownText
|
|
if (pullDownDistance > this.refresherThreshold) {
|
|
this.$emit('refreshReady')
|
|
}
|
|
}
|
|
this.$emit('touchMove', e)
|
|
},
|
|
// 触摸松开处理
|
|
handleTouchEnd(e) {
|
|
if (this.disabled) return
|
|
// 处理下拉刷新
|
|
if (this.showPullDown) {
|
|
// 当下拉高度小于下拉阈值
|
|
if (this.pullDownHeight < this.refresherThreshold) {
|
|
// 关闭正在下拉
|
|
this.pulldowning = false
|
|
// 重置下拉高度
|
|
this.pullDownHeight = 0
|
|
// 隐藏下拉刷新
|
|
this.showPullDown = false
|
|
// 触发下拉中断事件
|
|
this.$emit('refreshStop')
|
|
} else {
|
|
this.pullDownHeight = this.pullDownHeight > this.refresherThreshold ? this.refresherThreshold : this.pullDownHeight
|
|
this.refresh()
|
|
}
|
|
}
|
|
|
|
// 触发下拉触摸松开事件
|
|
this.$emit('touchEnd', e)
|
|
},
|
|
|
|
// 刷新数据
|
|
refresh() {
|
|
// 设置刷新未完成
|
|
this.refreshFinish = false
|
|
// 开启正在刷新
|
|
this.refreshing = true
|
|
// 设置正在刷新状态文字
|
|
this.refreshStateText = this.refreshingText
|
|
// 触发refresh事件
|
|
setTimeout(() => {
|
|
this.$emit('refresh')
|
|
}, this.refreshDelayed)
|
|
},
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.tn-scroll-view {
|
|
|
|
.scroll-view {
|
|
position: relative;
|
|
touch-action: none;
|
|
|
|
.scroll__content {
|
|
display: flex;
|
|
will-change: transform;
|
|
flex-direction: column;
|
|
|
|
.scroll {
|
|
&__pull-down {
|
|
position: absolute;
|
|
left: 0;
|
|
width: 100%;
|
|
display: flex;
|
|
padding: 30rpx 0;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transform: translateY(-100%);
|
|
}
|
|
|
|
&__refresh {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
&--text {
|
|
margin-left: 10rpx;
|
|
}
|
|
}
|
|
|
|
&__data {
|
|
|
|
}
|
|
|
|
&__pull-up {
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 0;
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transform: translateY(100%);
|
|
}
|
|
&__load {
|
|
padding: 20rpx 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
&--text {
|
|
margin-left: 10rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|