UNPKG

@v4fire/client

Version:

V4Fire client core library

175 lines (138 loc) 4.02 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ import symbolGenerator from 'core/symbol'; import Super from 'core/dom/in-view/super'; import { hasIntersection } from 'core/dom/in-view/intersection/helpers'; import type { InViewObservableElement } from 'core/dom/in-view/interface'; export type AdapteeType = 'observer'; export const $$ = symbolGenerator(); export default class InView extends Super { /** * Adaptee type */ readonly type: AdapteeType = 'observer'; /** * True if the current adaptee can be used */ static readonly acceptable: boolean = hasIntersection; /** * Contains IntersectionObserver instances */ protected readonly observers: Map<string, IntersectionObserver> = new Map(); /** * Map of ids for root elements */ protected readonly rootMap: Map<Element, number> = new Map(); protected override initObserve(observable: InViewObservableElement): InViewObservableElement { const observer = this.createObserver(observable); observer.observe(observable.node); this.putInMap(this.elements, observable); return observable; } protected override remove(observable: InViewObservableElement, suspend?: boolean): boolean { super.remove(observable, suspend); const observer = this.observers.get(observable.id); if (observer) { observer.unobserve(observable.node); this.observers.delete(observable.id); return true; } return false; } /** * Creates a new IntersectionObserver instance * @param observable */ protected createObserver(observable: InViewObservableElement): IntersectionObserver { const root = Object.isFunction(observable.root) ? observable.root() : observable.root, opts = {...observable, root}; delete opts.delay; const observer = new IntersectionObserver(this.onIntersects.bind(this, observable.threshold), opts); this.observers.set(observable.id, observer); return observer; } /** * Handler: entering or leaving viewport * * @param threshold * @param entries */ protected onIntersects(threshold: number, entries: IntersectionObserverEntry[]): void { for (let i = 0; i < entries.length; i++) { const entry = entries[i], el = entry.target, observable = this.getEl(el, threshold); if (!observable) { return; } this.setObservableSize(observable, entry.boundingClientRect); if (observable.isLeaving) { this.onObservableOut(observable, entry); } else if (entry.intersectionRatio >= observable.threshold) { this.onObservableIn(observable, entry); } } } /** * Handler: element becomes visible on viewport * * @param observable * @param entry */ protected onObservableIn(observable: InViewObservableElement, entry: IntersectionObserverEntry): void { const {async: $a} = this; const asyncOptions = { group: 'inView', label: observable.id, join: true }; observable.time = entry.time; observable.timeIn = entry.time; // eslint-disable-next-line @typescript-eslint/unbound-method if (Object.isFunction(observable.onEnter)) { observable.onEnter(observable); } if (observable.delay != null && observable.delay > 0) { $a.setTimeout(() => this.call(observable), observable.delay, asyncOptions); } else { this.call(observable); } observable.isLeaving = true; } /** * Handler: element leaves viewport * * @param observable * @param entry */ protected onObservableOut(observable: InViewObservableElement, entry: IntersectionObserverEntry): void { const {async: $a} = this; observable.time = entry.time; observable.timeOut = entry.time; const asyncOptions = { group: 'inView', label: observable.id, join: true }; // eslint-disable-next-line @typescript-eslint/unbound-method if (Object.isFunction(observable.onLeave)) { observable.onLeave(observable); } $a.clearAll(asyncOptions); observable.isLeaving = false; } }