@v4fire/client
Version:
V4Fire client core library
175 lines (138 loc) • 4.02 kB
text/typescript
/*!
* 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;
}
}