UNPKG

pull2refresh

Version:
144 lines (118 loc) 3.95 kB
export interface PullToRefreshOptions { onPull?: (percentage: number) => void; onRefresh: () => void; pullableElement: HTMLElement; refreshElement: HTMLElement; threshold?: number; } export default class PullToRefresh { private options: PullToRefreshOptions; private maxHeight: number; private initalMarginTop: number; private startY: number | null = null; private refreshInProgress = false; constructor(options: PullToRefreshOptions) { this.options = options; this.options.refreshElement.style.display = "block"; this.maxHeight = this.computeOffsetHeight(options.refreshElement); this.initalMarginTop = this.computeMarginTop(this.options.pullableElement); if (!this.options.threshold) { this.options.threshold = this.maxHeight; } this.reset(); this.options.pullableElement.addEventListener("touchstart", this.start.bind(this)); this.options.pullableElement.addEventListener("mousedown", this.start.bind(this)); document.addEventListener("touchmove", this.move.bind(this)); document.addEventListener("mousemove", this.move.bind(this)); document.addEventListener("touchend", this.end.bind(this)); document.addEventListener("mouseup", this.end.bind(this)); } public done() { this.refreshInProgress = false; this.reset(); } private reset() { this.startY = null; this.options.pullableElement.style.marginTop = this.initalMarginTop - this.maxHeight + "px"; } private start(event: MouseEvent | TouchEvent) { if (!this.isScrollToTop()) { // Don't pull to refresh when scroll bar is not at top return; } if (this.refreshInProgress) { return; } this.reset(); this.startY = this.pageY(event); } private move(event: MouseEvent | TouchEvent) { if (this.startY === null || this.refreshInProgress) { return; } const delta = this.pageY(event) - this.startY; if (delta <= 0) { return; } // Display the refresh element let margin = this.maxHeight - delta; if (margin < 0) { margin = 0; } this.options.pullableElement.style.marginTop = -margin + "px"; // Callback if (this.options.onPull && this.options.threshold) { const ratio = delta / this.options.threshold; this.options.onPull(ratio > 1 ? 1 : ratio); } } private end(event: MouseEvent | TouchEvent) { if (this.refreshInProgress) { return; } if (this.startY !== null) { const delta = this.pageY(event) - this.startY; if (this.options.threshold && delta >= this.options.threshold) { this.refreshInProgress = true; this.options.onRefresh(); return; } } this.reset(); } private pageY(event: MouseEvent | TouchEvent): number { if (event instanceof MouseEvent) { return event.pageY; } else { if (event.touches && event.touches.length) { return event.touches[event.touches.length - 1].pageY; } } return 0; } private computeOffsetHeight(element: HTMLElement): number { if (element.offsetHeight) { return element.offsetHeight; } const height = window.getComputedStyle(element).height; if (!height) { return 0; } const computed = parseInt(height, 10); if (isNaN(computed)) { return 0; } return computed; } private computeMarginTop(element: HTMLElement): number { const margin = parseInt(window.getComputedStyle(element).getPropertyValue("margin-top"), 10); if (isNaN(margin)) { return 0; } return margin; } private isScrollToTop(): boolean { return this.options.pullableElement.getBoundingClientRect().top === this.computeMarginTop(this.options.pullableElement); } } export type pull2refresh = PullToRefresh;