fix(domtools): stabilize DomTools lifecycle, cleanup, and singleton behavior
This commit is contained in:
@@ -3,19 +3,22 @@ import * as plugins from './domtools.plugins.js';
|
||||
|
||||
export class Scroller {
|
||||
public domtoolsInstance: DomTools;
|
||||
private disposed = false;
|
||||
|
||||
// Array to store scroll callback functions.
|
||||
private scrollCallbacks: Array<() => void> = [];
|
||||
|
||||
// Lenis instance (if activated) or null.
|
||||
private lenisInstance: plugins.Lenis | null = null;
|
||||
private lenisScrollUnsubscribe: (() => void) | null = null;
|
||||
private nativeScrollListenerAttached = false;
|
||||
|
||||
// Bound handlers to allow removal from event listeners.
|
||||
private handleNativeScroll = (event: Event): void => {
|
||||
private handleNativeScroll = (_event: Event): void => {
|
||||
this.executeScrollCallbacks();
|
||||
};
|
||||
|
||||
private handleLenisScroll = (info: any): void => {
|
||||
private handleLenisScroll = (_info: plugins.Lenis): void => {
|
||||
this.executeScrollCallbacks();
|
||||
};
|
||||
|
||||
@@ -44,23 +47,40 @@ export class Scroller {
|
||||
* Detects whether native smooth scrolling is enabled.
|
||||
*/
|
||||
public async detectNativeSmoothScroll() {
|
||||
const rootScrollBehavior = getComputedStyle(document.documentElement).scrollBehavior;
|
||||
const bodyScrollBehavior = document.body ? getComputedStyle(document.body).scrollBehavior : 'auto';
|
||||
if (rootScrollBehavior === 'smooth' || bodyScrollBehavior === 'smooth') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const done = plugins.smartpromise.defer<boolean>();
|
||||
const sampleSize = 100;
|
||||
const sampleSize = 12;
|
||||
const acceptableDeltaDifference = 3;
|
||||
const minimumSmoothRatio = 0.75;
|
||||
const timeoutInMs = 1200;
|
||||
|
||||
const eventDeltas: number[] = [];
|
||||
|
||||
const finalize = (result: boolean) => {
|
||||
window.removeEventListener('wheel', onWheel);
|
||||
window.clearTimeout(timeoutId);
|
||||
done.resolve(result);
|
||||
};
|
||||
|
||||
function onWheel(event: WheelEvent) {
|
||||
eventDeltas.push(event.deltaY);
|
||||
|
||||
if (eventDeltas.length >= sampleSize) {
|
||||
window.removeEventListener('wheel', onWheel);
|
||||
analyzeEvents();
|
||||
}
|
||||
}
|
||||
|
||||
function analyzeEvents() {
|
||||
if (eventDeltas.length < 2) {
|
||||
finalize(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const totalDiffs = eventDeltas.length - 1;
|
||||
let smallDiffCount = 0;
|
||||
|
||||
@@ -72,16 +92,14 @@ export class Scroller {
|
||||
}
|
||||
|
||||
const smoothRatio = smallDiffCount / totalDiffs;
|
||||
if (smoothRatio >= minimumSmoothRatio) {
|
||||
console.log('Smooth scrolling detected.');
|
||||
done.resolve(true);
|
||||
} else {
|
||||
console.log('Smooth scrolling NOT detected.');
|
||||
done.resolve(false);
|
||||
}
|
||||
finalize(smoothRatio >= minimumSmoothRatio);
|
||||
}
|
||||
|
||||
window.addEventListener('wheel', onWheel);
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
analyzeEvents();
|
||||
}, timeoutInMs);
|
||||
|
||||
window.addEventListener('wheel', onWheel, { passive: true });
|
||||
return done.promise;
|
||||
}
|
||||
|
||||
@@ -91,6 +109,14 @@ export class Scroller {
|
||||
* Lenis will be destroyed immediately.
|
||||
*/
|
||||
public async enableLenisScroll(optionsArg?: { disableOnNativeSmoothScroll?: boolean }) {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lenisInstance) {
|
||||
this.disableLenisScroll();
|
||||
}
|
||||
|
||||
const lenis = new plugins.Lenis({
|
||||
autoRaf: true,
|
||||
});
|
||||
@@ -107,24 +133,28 @@ export class Scroller {
|
||||
// Switch from native scroll listener to Lenis scroll listener.
|
||||
this.detachNativeScrollListener();
|
||||
this.attachLenisScrollListener();
|
||||
}
|
||||
|
||||
// Monkey-patch the destroy method so that when Lenis is destroyed,
|
||||
// the native scroll listener is reattached.
|
||||
const originalDestroy = lenis.destroy.bind(lenis);
|
||||
lenis.destroy = () => {
|
||||
originalDestroy();
|
||||
this.detachLenisScrollListener();
|
||||
this.attachNativeScrollListener();
|
||||
this.lenisInstance = null;
|
||||
};
|
||||
public disableLenisScroll() {
|
||||
if (!this.lenisInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.detachLenisScrollListener();
|
||||
this.lenisInstance.destroy();
|
||||
this.lenisInstance = null;
|
||||
this.attachNativeScrollListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be executed on scroll.
|
||||
* @param callback A function to execute on each scroll event.
|
||||
*/
|
||||
public onScroll(callback: () => void): void {
|
||||
public onScroll(callback: () => void): () => void {
|
||||
this.scrollCallbacks.push(callback);
|
||||
return () => {
|
||||
this.scrollCallbacks = this.scrollCallbacks.filter((existingCallback) => existingCallback !== callback);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,23 +175,30 @@ export class Scroller {
|
||||
* Attaches the native scroll event listener.
|
||||
*/
|
||||
private attachNativeScrollListener(): void {
|
||||
if (this.nativeScrollListenerAttached || this.disposed) {
|
||||
return;
|
||||
}
|
||||
window.addEventListener('scroll', this.handleNativeScroll);
|
||||
this.nativeScrollListenerAttached = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the native scroll event listener.
|
||||
*/
|
||||
private detachNativeScrollListener(): void {
|
||||
if (!this.nativeScrollListenerAttached) {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener('scroll', this.handleNativeScroll);
|
||||
this.nativeScrollListenerAttached = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the Lenis scroll event listener.
|
||||
*/
|
||||
private attachLenisScrollListener(): void {
|
||||
if (this.lenisInstance) {
|
||||
// Assuming that Lenis exposes an `on` method to listen to scroll events.
|
||||
this.lenisInstance.on('scroll', this.handleLenisScroll);
|
||||
if (this.lenisInstance && !this.lenisScrollUnsubscribe) {
|
||||
this.lenisScrollUnsubscribe = this.lenisInstance.on('scroll', this.handleLenisScroll);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +206,23 @@ export class Scroller {
|
||||
* Detaches the Lenis scroll event listener.
|
||||
*/
|
||||
private detachLenisScrollListener(): void {
|
||||
if (this.lenisInstance) {
|
||||
// Assuming that Lenis exposes an `off` method to remove scroll event listeners.
|
||||
this.lenisInstance.off('scroll', this.handleLenisScroll);
|
||||
if (this.lenisScrollUnsubscribe) {
|
||||
this.lenisScrollUnsubscribe();
|
||||
this.lenisScrollUnsubscribe = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
this.detachLenisScrollListener();
|
||||
this.lenisInstance?.destroy();
|
||||
this.lenisInstance = null;
|
||||
this.detachNativeScrollListener();
|
||||
this.scrollCallbacks = [];
|
||||
this.sweetScroller.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user