pull2refresh
Version:
Pull to refresh library
144 lines (118 loc) • 3.95 kB
text/typescript
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;