[Perf] useSharedIntersectionObserver: Consider heavy animation

This commit is contained in:
Alexander Zinchuk 2024-09-19 20:43:44 +02:00
parent 7634094883
commit 94a9f7eba8
2 changed files with 36 additions and 9 deletions

View File

@ -1,9 +1,9 @@
import { useEffect } from '../lib/teact/teact';
import { getIsHeavyAnimating, useEffect } from '../lib/teact/teact';
import type { CallbackManager } from '../util/callbacks';
import { createCallbackManager } from '../util/callbacks';
import { useStateRef } from './useStateRef';
import useLastCallback from './useLastCallback';
const elementObserverMap = new Map<HTMLElement, [IntersectionObserver, CallbackManager]>();
@ -12,7 +12,7 @@ export default function useSharedIntersectionObserver(
onIntersectionChange: (entry: IntersectionObserverEntry) => void,
isDisabled = false,
) {
const onIntersectionChangeRef = useStateRef(onIntersectionChange);
const onIntersectionChangeLast = useLastCallback(onIntersectionChange);
useEffect(() => {
const el = refOrElement && 'current' in refOrElement ? refOrElement.current : refOrElement;
@ -20,13 +20,29 @@ export default function useSharedIntersectionObserver(
return undefined;
}
const callback: IntersectionObserverCallback = ([entry]) => {
// Ignore updates when element is not properly mounted (`display: none`)
if (!(entry.target as HTMLElement).offsetWidth || !(entry.target as HTMLElement).offsetHeight) {
return;
const entriesAccumulator = new Map<Element, IntersectionObserverEntry>();
function flush() {
for (const entry of entriesAccumulator.values()) {
// Ignore updates when element is not properly mounted (`display: none`)
if (!(entry.target as HTMLElement).offsetParent) {
continue;
}
onIntersectionChangeLast(entry);
}
onIntersectionChangeRef.current(entry);
entriesAccumulator.clear();
}
const callback: IntersectionObserverCallback = ([entry]) => {
entriesAccumulator.set(entry.target, entry);
if (!getIsHeavyAnimating()) {
flush();
} else {
getIsHeavyAnimating.once(flush);
}
};
let [observer, callbackManager] = elementObserverMap.get(el) || [undefined, undefined];
@ -46,5 +62,5 @@ export default function useSharedIntersectionObserver(
elementObserverMap.delete(el);
}
};
}, [isDisabled, onIntersectionChangeRef, refOrElement]);
}, [isDisabled, refOrElement]);
}

View File

@ -12,6 +12,7 @@ const SIGNAL_MARK = Symbol('SIGNAL_MARK');
export type Signal<T = any> = ((() => T) & {
readonly [SIGNAL_MARK]: symbol;
subscribe: (cb: AnyToVoidFunction) => NoneToVoidFunction;
once: (cb: AnyToVoidFunction) => NoneToVoidFunction;
});
export type SignalSetter = (newValue: any) => void;
@ -51,6 +52,15 @@ export function createSignal<T>(defaultValue?: T) {
};
}
function once(effect: NoneToVoidFunction) {
const unsub = subscribe(() => {
unsub();
effect();
});
return unsub;
}
function getter() {
if (currentEffect) {
subscribe(currentEffect);
@ -71,6 +81,7 @@ export function createSignal<T>(defaultValue?: T) {
const signal = Object.assign(getter as Signal<T>, {
[SIGNAL_MARK]: SIGNAL_MARK,
subscribe,
once,
});
return [signal, setter] as const;