diff --git a/.eslintrc b/.eslintrc index 9dfa9a0e8..045e29106 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,7 +44,7 @@ "react-hooks-static-deps/exhaustive-deps": [ "error", { - "additionalHooks": "(useSyncEffect|useAsync|useDebouncedCallback|useThrottledCallback|useEffectWithPrevDeps|useLayoutEffectWithPrevDeps|useDerivedState|useDerivedSignal|useThrottledResolver|useDebouncedResolver)$", + "additionalHooks": "(useSyncEffect|useAsync|useDebouncedCallback|useThrottledCallback|useEffectWithPrevDeps|useLayoutEffectWithPrevDeps|useDerivedState|useDerivedSignal|useThrottledResolver|useDebouncedResolver|useThrottleForHeavyAnimation)$", "staticHooks": { "getActions": true, "useFlag": [false, true, true], diff --git a/src/components/common/embedded/EmojiIconBackground.tsx b/src/components/common/embedded/EmojiIconBackground.tsx index d0d0540d5..e5e487ce4 100644 --- a/src/components/common/embedded/EmojiIconBackground.tsx +++ b/src/components/common/embedded/EmojiIconBackground.tsx @@ -9,9 +9,7 @@ import { preloadImage } from '../../../util/files'; import { REM } from '../helpers/mediaDimensions'; import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener'; -import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps'; -import useFlag from '../../../hooks/useFlag'; -import useHeavyAnimationCheck, { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck'; +import { useThrottleForHeavyAnimation } from '../../../hooks/useHeavyAnimationCheck'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useMedia from '../../../hooks/useMedia'; @@ -79,21 +77,20 @@ const EmojiIconBackground = ({ const lang = useLang(); - // Delay mounting until heavy animation ends - const [canUpdate, markCanUpdate, unmarkCanUpdate] = useFlag(!isHeavyAnimating()); - useHeavyAnimationCheck(unmarkCanUpdate, markCanUpdate); - const { customEmoji } = useCustomEmoji(emojiDocumentId); const previewMediaHash = customEmoji ? getStickerPreviewHash(customEmoji.id) : undefined; const previewUrl = useMedia(previewMediaHash); const customColor = useDynamicColorListener(containerRef); - useEffect(() => { - if (!previewUrl || !canUpdate) return; - + const preloadAfterHeavyAnimation = useThrottleForHeavyAnimation(() => { + if (!previewUrl) return; preloadImage(previewUrl).then(setEmojiImage); - }, [previewUrl, canUpdate]); + }, [previewUrl]); + + useEffect(() => { + preloadAfterHeavyAnimation(); + }, [preloadAfterHeavyAnimation]); const updateCanvas = useLastCallback(() => { const canvas = canvasRef.current; @@ -129,46 +126,43 @@ const EmojiIconBackground = ({ context.restore(); }); - useEffectWithPrevDeps(([prevEmojiImage, prevLangRtl, prevCustomColor]) => { - // No need to trigger update if only `canUpdate` changed - if (emojiImage === prevEmojiImage && lang.isRtl === prevLangRtl && customColor === prevCustomColor) return; - updateCanvas(); - }, [emojiImage, lang.isRtl, customColor, canUpdate]); + const updateCanvasAfterHeavyAnimation = useThrottleForHeavyAnimation(updateCanvas, [updateCanvas]); - const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => { - const canvas = canvasRef.current; - if (!canvas || isHeavyAnimating()) return; + useEffect(() => { + updateCanvasAfterHeavyAnimation(); + }, [emojiImage, lang.isRtl, customColor, updateCanvasAfterHeavyAnimation]); - canvas.width = parentWidth * dpr; - canvas.height = parentHeight * dpr; + const updateCanvasSize = useThrottleForHeavyAnimation((parentWidth: number, parentHeight: number) => { + requestMutation(() => { + const canvas = canvasRef.current; + if (!canvas) return; - canvas.style.width = `${parentWidth}px`; - canvas.style.height = `${parentHeight}px`; + canvas.width = parentWidth * dpr; + canvas.height = parentHeight * dpr; - updateCanvas(); - }); + canvas.style.width = `${parentWidth}px`; + canvas.style.height = `${parentHeight}px`; - const handleResize = useLastCallback((entry: ResizeObserverEntry) => { + updateCanvas(); + }); + }, [dpr]); + + const handleResize = useThrottleForHeavyAnimation((entry: ResizeObserverEntry) => { const { width, height } = entry.contentRect; - requestMutation(() => { - updateCanvasSize(width, height); - }); - }); + updateCanvasSize(width, height); + }, [updateCanvasSize]); - useResizeObserver(containerRef, handleResize, !canUpdate); + useResizeObserver(containerRef, handleResize); - useEffectWithPrevDeps(([prevDpr]) => { - if (dpr === prevDpr) return; + useEffect(() => { const container = containerRef.current; - if (!container || !canUpdate) return; + if (!container) return; const { width, height } = container.getBoundingClientRect(); - requestMutation(() => { - updateCanvasSize(width, height); - }); - }, [dpr, canUpdate]); + updateCanvasSize(width, height); + }, [updateCanvasSize]); return (
diff --git a/src/hooks/useHeavyAnimationCheck.ts b/src/hooks/useHeavyAnimationCheck.ts index d69e5f3f4..7a54172f5 100644 --- a/src/hooks/useHeavyAnimationCheck.ts +++ b/src/hooks/useHeavyAnimationCheck.ts @@ -1,4 +1,6 @@ -import { useEffect } from '../lib/teact/teact'; +import { + useCallback, useEffect, useMemo, useRef, +} from '../lib/teact/teact'; import { createCallbackManager } from '../util/callbacks'; import useLastCallback from './useLastCallback'; @@ -39,6 +41,32 @@ const useHeavyAnimationCheck = ( }, [isDisabled, lastOnEnd, lastOnStart]); }; +export function useThrottleForHeavyAnimation(afterHeavyAnimation: T, deps: unknown[]) { + // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps + const fnMemo = useCallback(afterHeavyAnimation, deps); + + const isScheduledRef = useRef(false); + + return useMemo(() => { + return (...args: Parameters) => { + if (!isScheduledRef.current) { + if (!isAnimating) { + fnMemo(...args); + return; + } + + isScheduledRef.current = true; + + const removeCallback = endCallbacks.addCallback(() => { + fnMemo(...args); + removeCallback(); + isScheduledRef.current = false; + }); + } + }; + }, [fnMemo]); +} + export function isHeavyAnimating() { return isAnimating; }