UNPKG

easy-pullrefresh

Version:

A lightweight JS library for pull to refresh

102 lines (86 loc) 3.4 kB
interface PullRefreshOptions { container: HTMLElement onRefresh: () => Promise<void> indicatorRender?: (status: 'idle' | 'pulling' | 'loading', distance: number) => string threshold?: number } function initPullRefresh({ container, onRefresh, indicatorRender, threshold = 60 }: PullRefreshOptions) { function dampedPull(deltaY: number): number { if (deltaY <= threshold) return deltaY return threshold + (deltaY - threshold) * 0.2 } function updateStatus(status: 'idle' | 'pulling' | 'loading', distance: number) { indicator.innerHTML = indicatorRender?.(status, distance) || '' } async function onTouchEnd() { if (!pulling) return pulling = false indicator.style.transition = 'transform 0.3s' container.style.transition = 'all 0.3s' if (distance >= threshold) { updateStatus('loading', 0) container.style.transform = `translateY(${threshold}px)` container.style.clipPath = `inset(0px 0px calc(${threshold}px))` indicator.style.transform = `translateY(${threshold}px)` await onRefresh() } container.style.transform = 'translateY(0)' container.style.clipPath = `inset(0px 0px calc(0px))` indicator.style.visibility = 'hidden' updateStatus('idle', 0) distance = 0 vibrateOnce = false } const { top } = container.getBoundingClientRect() const parentNode = container.parentNode as HTMLElement parentNode.style.position ||= 'relative' let vibrateOnce = false const indicator = document.createElement('div') indicator.style.cssText = ` position: fixed; top: ${top - 40}px; left: 0; width: 100%; text-align: center; pointer-events: none; z-index: 10; ` parentNode.prepend(indicator) container.style.willChange = 'transform' indicator.style.willChange = 'transform' let pulling = false let startY = 0 let distance = 0 container.addEventListener('touchstart', (e) => { if (container.scrollTop === 0) { startY = e.touches[0].clientY pulling = true } }) container.addEventListener('touchmove', (e) => { if (!pulling) return const deltaY = e.touches[0].clientY - startY if (deltaY <= 0) return if (e.cancelable) { e.preventDefault() e.stopPropagation() } distance = dampedPull(deltaY) container.style.transition = 'transform 0s' indicator.style.transition = 'transform 0s' indicator.style.visibility = 'visible' requestAnimationFrame(() => { container.style.transform = `translateY(${distance}px)` indicator.style.transform = `translateY(${distance}px)` container.style.clipPath = `inset(0px 0px calc(${distance}px))` updateStatus('pulling', distance) if (!vibrateOnce && distance >= threshold) { navigator.vibrate(1000) vibrateOnce = true } }) }, { passive: false }) container.addEventListener('touchend', onTouchEnd) container.addEventListener('touchcancel', onTouchEnd) } export default initPullRefresh