diff --git a/src/hooks/useSharedIntersectionObserver.ts b/src/hooks/useSharedIntersectionObserver.ts index 5d942a2a4..8a4b96cd5 100644 --- a/src/hooks/useSharedIntersectionObserver.ts +++ b/src/hooks/useSharedIntersectionObserver.ts @@ -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(); @@ -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(); + + 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]); } diff --git a/src/util/signals.ts b/src/util/signals.ts index 09dbca1bb..af390c2cb 100644 --- a/src/util/signals.ts +++ b/src/util/signals.ts @@ -12,6 +12,7 @@ const SIGNAL_MARK = Symbol('SIGNAL_MARK'); export type Signal = ((() => 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(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(defaultValue?: T) { const signal = Object.assign(getter as Signal, { [SIGNAL_MARK]: SIGNAL_MARK, subscribe, + once, }); return [signal, setter] as const;