tqpulldownrefresh
Version:
175 lines (134 loc) • 4.13 kB
JavaScript
import { nextFrame, cancelFrame, slideTo } from './animate.js'
const masDragDistance = 300 // 最大拖动距离
const map = new Map()
let timer = null
export default class TQPullDownRefresh {
constructor(options) {
this.state = {
x: 0,
y: 0,
endY: 0,
ladderDistance: 0,
lastY: 0,
rate: 1,
isRunning: false,
}
this.isSpringBack = false
this.containerRef = null
this.areaRect = null
this.refresherRef = null
this.refresherRect = null
this.onPullStart = null
this.onReleaseToRefresh = null
this.onPulling = null
this.containerRef = options.containerRef
this.areaRect = this.containerRef.getBoundingClientRect()
this.refresherRef = options.refresherRef
this.refresherRect = this.refresherRef.getBoundingClientRect()
// 避免重复引用
if (map.get(options.containerRef)) {
return
}
map.set(options.containerRef, true)
this.containerRef.addEventListener('touchstart', this.touchStart.bind(this))
this.containerRef.addEventListener('touchmove', this.touchMove.bind(this))
this.containerRef.addEventListener('touchend', this.touchEnd.bind(this))
}
prepare(touch) {
this.refresherRect = this.refresherRef.getBoundingClientRect()
this.state.x = this.refresherRect.left
this.state.y = this.refresherRect.top
this.state.lastY = touch.clientY
this.state.isRunning = true
}
destroy() {
this.containerRef.removeEventListener('touchstart', this.touchStart.bind(this))
this.containerRef.removeEventListener('touchmove', this.touchMove.bind(this))
this.containerRef.removeEventListener('touchend', this.touchEnd.bind(this))
}
// 记录日志:动画结束的时机
rotate(angle) {
console.log('旋转动画开始')
cancelFrame(timer)
this.animate(angle)
}
animate(angle) {
this.refresherRef.style.transform = `translate(0px, ${this.state.y}px) rotate(${angle}deg`
angle = angle === -360 ? 0 : angle - 5
timer = nextFrame(() => {
this.animate(angle - 5)
})
}
// 回弹
springBack() {
console.log('旋转动画结束')
console.log(`打印要结束的旋转动画 id: ${timer}`)
cancelFrame(timer)
this.state.y = 0
const rect = this.refresherRef.getBoundingClientRect()
console.log('回弹动画开始')
slideTo(this.refresherRef, rect.top, () => {
this.isSpringBack = true
this.state.isRunning = false
})
this.state.rate = 1
this.state.lastY = 0
}
move(touch) {
const dy = touch.clientY - this.state.lastY
this.state.lastY = touch.clientY
this.state.rate = this.getRate(dy, this.state.rate)
this.state.y += dy * this.state.rate
if (this.state.y >= masDragDistance) {
this.state.y = masDragDistance
}
if (this.state.y < 0) {
this.state.y = 0
}
this.dragMove(this.state.y)
}
// 手势
getRate(distance, rate) {
this.state.ladderDistance += distance
if (distance > 0) {
if (this.state.ladderDistance >= 100) {
rate = Math.max(0, rate - 0.1)
this.state.ladderDistance = 0
}
} else {
if (this.state.ladderDistance < -100) {
rate = Math.min(1, rate + 0.1)
this.state.ladderDistance = 0
}
}
return rate
}
dragMove(translateY) {
if (!this.refresherRef)
return
const angle = ~~(translateY / masDragDistance * 360) * -1
this.refresherRef.style.transform = `translate(0px, ${translateY}px) rotate(${angle}deg)`
this.onPulling && this.onPulling(translateY)
}
touchStart(e) {
const touch = e.touches[0]
this.prepare(touch)
this.isSpringBack = false
this.onPullStart && this.onPullStart()
}
touchEnd(e) {
if (this.state.y >= masDragDistance) {
console.log('触发旋转动画')
this.rotate(0)
this.onReleaseToRefresh && this.onReleaseToRefresh()
} else {
console.log('触发回弹动画')
this.springBack()
}
this.destroy()
}
touchMove(e) {
const touch = e.touches[0]
this.move(touch)
}
}