Emoji Background: Fix resize after heavy animation (#4656)

This commit is contained in:
zubiden 2024-06-12 18:11:16 +02:00 committed by Alexander Zinchuk
parent 61339c3bf8
commit 1997008ba5
3 changed files with 62 additions and 40 deletions

View File

@ -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],

View File

@ -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 (
<div className={buildClassName(styles.root, className)} ref={containerRef}>

View File

@ -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<T extends AnyToVoidFunction>(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<T>) => {
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;
}