學(xué)習(xí)了有贊pullRefresh的組件宣旱,自己寫了一個(gè)下拉刷新的組件霎肯。上碼:
<template>
<div class="van-pull-down-refresh">
<div
ref="pullDownRefresh"
class="my_track"
:style="trackStyle"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
@touchcancel="onTouchEnd"
>
<slot></slot>
<div class="my_bottom">
<div v-if="showStatusText" class="text"> {{ this.getStatusText() }} </div>
<van-loading size="16" > {{ this.getStatusText() }} </van-loading>
</div>
</div>
</div>
</template>
<script>
const MIN_DISTANCE = 10
const DEFAULT_BOTTOM_HEIGHT = 50
const TEXT_STATUS = ['pulling', 'loosing', 'success']
export default {
props: {
disabled: {
type: Boolean,
default: () => []
},
successText: String,
pullingText: String,
loosingText: String,
loadingText: String,
value: {
type: Boolean,
default: false
}
},
data () {
return {
successDuration: 500,
animationDuration: 300,
// 下拉的最大高度
headHeight: DEFAULT_BOTTOM_HEIGHT,
state: {
status: 'normal',
distance: 0,
duration: 0
},
reachBottom: null,
// 記入第一次點(diǎn)擊的位置
startTouch: {}
}
},
computed: {
trackStyle () {
return {
transitionDuration: `${this.state.duration}ms`,
transform: this.state.distance
? `translate3d(0,${this.state.distance}px, 0)`
: ''
}
}
},
watch: { // 監(jiān)聽外部傳參的變化
value (val) {
if (val) {
this.setStatus(this.headHeight, true)
} else {
this.setStatus(0, false)
}
}
},
methods: {
showStatusText () {
return TEXT_STATUS.indexOf(this.state.status) !== -1
},
// 是否可以觸發(fā)touch事件
isTouchable () {
return this.state.status !== 'loading' && this.state.status !== 'success' && !this.disabled
},
ease (dis) {
const headHeight = this.headHeight
let distance = Math.abs(dis)
if (distance > headHeight) {
if (distance < headHeight * 2) {
distance = headHeight + (distance - headHeight) / 2
} else {
distance = headHeight * 1.5 + (distance - headHeight * 2) / 4
}
}
return -Math.round(distance)
},
setStatus (distance, isLoading) {
this.state.distance = distance
if (isLoading) {
this.state.status = 'loading'
} else if (distance === 0) {
this.state.status = 'normal'
} else if (Math.abs(distance) < this.headHeight) {
this.state.status = 'pulling'
} else {
this.state.status = 'loosing'
}
},
getStatusText () {
const { status } = this.state
if (status === 'normal') {
return ''
}
return this[`${status}Text`]
},
getRootOffsetHeight () {
return (
window.innerHeight ||
document.documentElement.offsetHeight ||
document.body.offsetHeight ||
0
)
},
checkPosition (event) {
this.reachBottom = this.$refs.pullDownRefresh.getBoundingClientRect().bottom < this.getRootOffsetHeight()
if (this.reachBottom) {
this.state.duration = 0
this.startTouch = event.touches[0]
}
},
onTouchStart (event) {
if (this.isTouchable()) {
this.checkPosition(event)
}
},
getDirection (x, y) {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal'
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical'
}
return ''
},
onTouchMove (event) {
if (this.isTouchable() && this.startTouch.clientY) {
if (!this.reachBottom) {
this.checkPosition(event)
}
const deltaX = event.touches[0].clientX - this.startTouch.clientX
const deltaY = event.touches[0].clientY - this.startTouch.clientY
const offsetX = Math.abs(deltaX)
const offsetY = Math.abs(deltaY)
this.deltaY = deltaY
if (this.reachBottom && deltaY <= 0 && this.getDirection(offsetX, offsetY) === 'vertical') {
if (event.cancelable) {
event.preventDefault()
}
this.setStatus(this.ease(deltaY))
}
}
},
onTouchEnd () {
if (this.reachBottom && this.deltaY && this.isTouchable()) {
this.state.duration = this.animationDuration
if (this.state.status === 'loosing') {
this.setStatus(this.headHeight, true)
this.$emit('update:value', true)
// ensure value change can be watched
this.$nextTick(() => {
this.$emit('refresh')
})
} else {
this.setStatus(0)
}
}
}
}
}
</script>
<style lang="less" scoped>
.van-pull-down-refresh {
overflow: hidden;
user-select: none;
.my_track {
position: relative;
height: 100%;
transition-property: transform;
}
.my_bottom {
position: absolute;
left: 0;
width: 100%;
height: 50px;
overflow: hidden;
font-size: 14px;
line-height: 50px;
color: #969799;
text-align: center;
transform: translateY(0%);
}
}
</style>
使用方法:
<template>
<pull-down-refresh
v-model="isLoading"
@refresh="onRefresh"
pullingText="上拉即可更新"
loosingText="釋放即可更新"
loadingText="更新中"
:disabled="isFinished"
>
<div class="list-box">
這里寫你的頁面內(nèi)容
</div>
</pull-down-refresh>
</template>
<script>
import PullDownRefresh from '@/components/PullDownRefresh'
export default {
name: 'xxx',
components: {
PullDownRefresh
},
data () {
return {
isLoading: false,
isFinished: false
}
},
methods: {
onRefresh () { ... }
}
}
</script>
備注: 有贊vant的組件中把touch事件的處理放到了工具文件scroll.js,并且用了vue3的新功能ref,用于保存第一次點(diǎn)擊的位置羞反,這樣處理更加優(yōu)雅簡(jiǎn)潔布朦。因?yàn)檫@里只有一個(gè)組件,我就寫到代碼里面昼窗。
是不是很簡(jiǎn)單好用 :)