Message List: Optimizations (#4595)

This commit is contained in:
zubiden 2024-06-12 18:10:37 +02:00 committed by Alexander Zinchuk
parent 1978419e84
commit c04014b7a1
19 changed files with 101 additions and 38 deletions

View File

@ -16,6 +16,7 @@ import { IS_ELECTRON } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter'; import useColorFilter from '../../hooks/stickers/useColorFilter';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useFlag from '../../hooks/useFlag';
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck'; import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck'; import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
@ -96,6 +97,16 @@ const AnimatedSticker: FC<OwnProps> = ({
const rgbColor = useRef<[number, number, number] | undefined>(); const rgbColor = useRef<[number, number, number] | undefined>();
const shouldForceOnHeavyAnimation = forceAlways || forceOnHeavyAnimation;
// Delay initialization until heavy animation ends
const [
canInitialize, markCanInitialize, unmarkCanInitialize,
] = useFlag(!isHeavyAnimating() || shouldForceOnHeavyAnimation);
useHeavyAnimationCheck(unmarkCanInitialize, markCanInitialize, shouldForceOnHeavyAnimation);
useEffect(() => {
if (shouldForceOnHeavyAnimation) markCanInitialize();
}, [shouldForceOnHeavyAnimation]);
useSyncEffect(() => { useSyncEffect(() => {
if (color && !shouldUseColorFilter) { if (color && !shouldUseColorFilter) {
const { r, g, b } = hexToRgb(color); const { r, g, b } = hexToRgb(color);
@ -118,6 +129,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|| isUnmountedRef.current || isUnmountedRef.current
|| !tgsUrl || !tgsUrl
|| (sharedCanvas && (!sharedCanvasCoords || !sharedCanvas.offsetWidth || !sharedCanvas.offsetHeight)) || (sharedCanvas && (!sharedCanvasCoords || !sharedCanvas.offsetWidth || !sharedCanvas.offsetHeight))
|| (isHeavyAnimating() && !shouldForceOnHeavyAnimation)
) { ) {
return; return;
} }
@ -154,12 +166,13 @@ const AnimatedSticker: FC<OwnProps> = ({
}); });
useEffect(() => { useEffect(() => {
if (!canInitialize) return;
if (getRLottie()) { if (getRLottie()) {
init(); init();
} else { } else {
ensureRLottie().then(init); ensureRLottie().then(init);
} }
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords]); }, [init, tgsUrl, sharedCanvas, sharedCanvasCoords, canInitialize]);
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS); const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
useSharedIntersectionObserver(sharedCanvas, throttledInit); useSharedIntersectionObserver(sharedCanvas, throttledInit);
@ -239,7 +252,7 @@ const AnimatedSticker: FC<OwnProps> = ({
} }
}, [playAnimation, animation, tgsUrl]); }, [playAnimation, animation, tgsUrl]);
useHeavyAnimationCheck(pauseAnimation, playAnimation, !playKey || forceAlways || forceOnHeavyAnimation); useHeavyAnimationCheck(pauseAnimation, playAnimation, !playKey || shouldForceOnHeavyAnimation);
usePriorityPlaybackCheck(pauseAnimation, playAnimation, !playKey || forceAlways); usePriorityPlaybackCheck(pauseAnimation, playAnimation, !playKey || forceAlways);
// Pausing frame may not happen in background, // Pausing frame may not happen in background,
// so we need to make sure it happens right after focusing, // so we need to make sure it happens right after focusing,

View File

@ -120,7 +120,7 @@ const File: FC<OwnProps> = ({
{withThumb && ( {withThumb && (
<canvas <canvas
ref={thumbRef} ref={thumbRef}
className={buildClassName('thumbnail', thumbClassNames)} className={buildClassName('thumbnail', 'canvas-blur-setup', thumbClassNames)}
/> />
)} )}
</div> </div>

View File

@ -154,7 +154,7 @@ const GifButton: FC<OwnProps> = ({
{withThumb && ( {withThumb && (
<canvas <canvas
ref={thumbRef} ref={thumbRef}
className="thumbnail" className="thumbnail canvas-blur-setup"
// We need to always render to avoid blur re-calculation // We need to always render to avoid blur re-calculation
style={isVideoReady ? 'display: none;' : undefined} style={isVideoReady ? 'display: none;' : undefined}
/> />

View File

@ -57,7 +57,12 @@ const MediaSpoiler: FC<OwnProps> = ({
ref={ref} ref={ref}
onClick={withAnimation ? handleClick : undefined} onClick={withAnimation ? handleClick : undefined}
> >
<canvas ref={canvasRef} className={styles.canvas} width={width} height={height} /> <canvas
ref={canvasRef}
className={buildClassName(styles.canvas, 'canvas-blur-setup')}
width={width}
height={height}
/>
<div className={styles.dots} /> <div className={styles.dots} />
</div> </div>
); );

View File

@ -119,7 +119,7 @@ const ProfilePhoto: FC<OwnProps> = ({
content = ( content = (
<> <>
{isBlurredThumb ? ( {isBlurredThumb ? (
<canvas ref={blurredThumbCanvasRef} className="thumb" /> <canvas ref={blurredThumbCanvasRef} className="thumb canvas-blur-setup" />
) : ( ) : (
<img src={avatarBlobUrl} draggable={false} className="thumb" alt="" /> <img src={avatarBlobUrl} draggable={false} className="thumb" alt="" />
)} )}

View File

@ -9,7 +9,7 @@ import { getStickerPreviewHash } from '../../global/helpers';
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors'; import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import * as mediaLoader from '../../util/mediaLoader'; import * as mediaLoader from '../../util/mediaLoader';
import { IS_ANDROID, IS_WEBM_SUPPORTED } from '../../util/windowEnvironment'; import { IS_WEBM_SUPPORTED } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter'; import useColorFilter from '../../hooks/stickers/useColorFilter';
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas'; import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
@ -115,8 +115,8 @@ const StickerView: FC<OwnProps> = ({
const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia); const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia);
// If Lottie data is loaded we will only render thumb if it's good enough (from preview) // If Lottie data is loaded we will only render thumb if it's good enough (from preview)
const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData)); const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData));
// Delay mounting on Android until heavy animation ends // Delay mounting until heavy animation ends
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!IS_ANDROID || !isHeavyAnimating()); const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!isHeavyAnimating());
useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount); useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount);
const isFullMediaReady = isReadyToMount && fullMediaData && (isStatic || isPlayerReady); const isFullMediaReady = isReadyToMount && fullMediaData && (isStatic || isPlayerReady);

View File

@ -9,6 +9,9 @@ import { preloadImage } from '../../../util/files';
import { REM } from '../helpers/mediaDimensions'; import { REM } from '../helpers/mediaDimensions';
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener'; import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
import useFlag from '../../../hooks/useFlag';
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia'; import useMedia from '../../../hooks/useMedia';
@ -76,6 +79,10 @@ const EmojiIconBackground = ({
const lang = useLang(); const lang = useLang();
// Delay mounting until heavy animation ends
const [canUpdate, markCanUpdate, unmarkCanUpdate] = useFlag(!isHeavyAnimating());
useHeavyAnimationCheck(unmarkCanUpdate, markCanUpdate);
const { customEmoji } = useCustomEmoji(emojiDocumentId); const { customEmoji } = useCustomEmoji(emojiDocumentId);
const previewMediaHash = customEmoji ? getStickerPreviewHash(customEmoji.id) : undefined; const previewMediaHash = customEmoji ? getStickerPreviewHash(customEmoji.id) : undefined;
const previewUrl = useMedia(previewMediaHash); const previewUrl = useMedia(previewMediaHash);
@ -83,10 +90,10 @@ const EmojiIconBackground = ({
const customColor = useDynamicColorListener(containerRef); const customColor = useDynamicColorListener(containerRef);
useEffect(() => { useEffect(() => {
if (!previewUrl) return; if (!previewUrl || !canUpdate) return;
preloadImage(previewUrl).then(setEmojiImage); preloadImage(previewUrl).then(setEmojiImage);
}, [previewUrl]); }, [previewUrl, canUpdate]);
const updateCanvas = useLastCallback(() => { const updateCanvas = useLastCallback(() => {
const canvas = canvasRef.current; const canvas = canvasRef.current;
@ -122,13 +129,15 @@ const EmojiIconBackground = ({
context.restore(); context.restore();
}); });
useEffect(() => { useEffectWithPrevDeps(([prevEmojiImage, prevLangRtl, prevCustomColor]) => {
// No need to trigger update if only `canUpdate` changed
if (emojiImage === prevEmojiImage && lang.isRtl === prevLangRtl && customColor === prevCustomColor) return;
updateCanvas(); updateCanvas();
}, [emojiImage, lang.isRtl, customColor]); }, [emojiImage, lang.isRtl, customColor, canUpdate]);
const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => { const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => {
const canvas = canvasRef.current; const canvas = canvasRef.current;
if (!canvas) return; if (!canvas || isHeavyAnimating()) return;
canvas.width = parentWidth * dpr; canvas.width = parentWidth * dpr;
canvas.height = parentHeight * dpr; canvas.height = parentHeight * dpr;
@ -147,18 +156,19 @@ const EmojiIconBackground = ({
}); });
}); });
useResizeObserver(containerRef, handleResize); useResizeObserver(containerRef, handleResize, !canUpdate);
useEffect(() => { useEffectWithPrevDeps(([prevDpr]) => {
if (dpr === prevDpr) return;
const container = containerRef.current; const container = containerRef.current;
if (!container) return; if (!container || !canUpdate) return;
const { width, height } = container.getBoundingClientRect(); const { width, height } = container.getBoundingClientRect();
requestMutation(() => { requestMutation(() => {
updateCanvasSize(width, height); updateCanvasSize(width, height);
}); });
}, [dpr]); }, [dpr, canUpdate]);
return ( return (
<div className={buildClassName(styles.root, className)} ref={containerRef}> <div className={buildClassName(styles.root, className)} ref={containerRef}>

View File

@ -36,6 +36,7 @@ type OwnProps = {
shouldPause?: boolean; shouldPause?: boolean;
shouldLoop?: boolean; shouldLoop?: boolean;
loopLimit?: number; loopLimit?: number;
shouldDelayInit?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
}; };
@ -66,6 +67,7 @@ const ReactionAnimatedEmoji = ({
shouldPause, shouldPause,
shouldLoop, shouldLoop,
loopLimit, loopLimit,
shouldDelayInit,
observeIntersection, observeIntersection,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { stopActiveReaction } = getActions(); const { stopActiveReaction } = getActions();
@ -167,7 +169,7 @@ const ReactionAnimatedEmoji = ({
size={size} size={size}
noPlay={shouldPause} noPlay={shouldPause}
loopLimit={loopLimit} loopLimit={loopLimit}
forceAlways forceAlways={!shouldDelayInit}
observeIntersectionForPlaying={observeIntersection} observeIntersectionForPlaying={observeIntersection}
/> />
)} )}
@ -179,7 +181,7 @@ const ReactionAnimatedEmoji = ({
tgsUrl={mediaDataCenterIcon} tgsUrl={mediaDataCenterIcon}
play={isIntersecting && !shouldPause} play={isIntersecting && !shouldPause}
noLoop={!shouldLoop} noLoop={!shouldLoop}
forceAlways forceAlways={!shouldDelayInit}
onLoad={markAnimationLoaded} onLoad={markAnimationLoaded}
onEnded={unmarkAnimationLoaded} onEnded={unmarkAnimationLoaded}
/> />
@ -193,7 +195,7 @@ const ReactionAnimatedEmoji = ({
tgsUrl={mediaDataEffect} tgsUrl={mediaDataEffect}
play={isIntersecting} play={isIntersecting}
noLoop noLoop
forceAlways forceAlways={!shouldDelayInit}
onEnded={handleEnded} onEnded={handleEnded}
/> />
{isCustom && !assignedEffectId && isIntersecting && ( {isCustom && !assignedEffectId && isIntersecting && (

View File

@ -83,7 +83,9 @@ function BaseStory({
className={fullClassName} className={fullClassName}
onClick={isConnected ? handleClick : undefined} onClick={isConnected ? handleClick : undefined}
> >
{!isExpired && isPreview && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />} {!isExpired && isPreview && (
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
)}
{shouldRender && ( {shouldRender && (
<> <>
<img <img

View File

@ -972,6 +972,7 @@ const Message: FC<OwnProps & StateProps> = ({
noRecentReactors={isChannel} noRecentReactors={isChannel}
tags={tags} tags={tags}
isCurrentUserPremium={isPremium} isCurrentUserPremium={isPremium}
getIsMessageListReady={getIsMessageListReady}
/> />
); );
} }
@ -1475,6 +1476,7 @@ const Message: FC<OwnProps & StateProps> = ({
observeIntersection={observeIntersectionForPlaying} observeIntersection={observeIntersectionForPlaying}
noRecentReactors={isChannel} noRecentReactors={isChannel}
tags={tags} tags={tags}
getIsMessageListReady={getIsMessageListReady}
/> />
)} )}
</div> </div>

View File

@ -199,7 +199,9 @@ const Photo: FC<OwnProps> = ({
style={style} style={style}
onClick={isUploading ? undefined : handleClick} onClick={isUploading ? undefined : handleClick}
> >
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />} {withBlurredBackground && (
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
)}
<img <img
src={fullMediaData} src={fullMediaData}
className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')} className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')}
@ -208,7 +210,10 @@ const Photo: FC<OwnProps> = ({
draggable={!isProtected} draggable={!isProtected}
/> />
{withThumb && ( {withThumb && (
<canvas ref={thumbRef} className={buildClassName('thumbnail', thumbClassNames)} /> <canvas
ref={thumbRef}
className={buildClassName('thumbnail', !noThumb && 'canvas-blur-setup', thumbClassNames)}
/>
)} )}
{isProtected && <span className="protector" />} {isProtected && <span className="protector" />}
{shouldRenderSpinner && !shouldRenderDownloadButton && ( {shouldRenderSpinner && !shouldRenderDownloadButton && (

View File

@ -260,7 +260,7 @@ const RoundVideo: FC<OwnProps> = ({
{!shouldRenderSpoiler && ( {!shouldRenderSpoiler && (
<canvas <canvas
ref={thumbRef} ref={thumbRef}
className={buildClassName('thumbnail', thumbClassNames)} className={buildClassName('thumbnail', 'canvas-blur-setup', thumbClassNames)}
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`} style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
/> />
)} )}

View File

@ -211,7 +211,9 @@ const Video: FC<OwnProps> = ({
style={style} style={style}
onClick={isUploading ? undefined : handleClick} onClick={isUploading ? undefined : handleClick}
> >
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />} {withBlurredBackground && (
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
)}
{isInline && ( {isInline && (
<OptimizedVideo <OptimizedVideo
ref={videoRef} ref={videoRef}
@ -237,7 +239,7 @@ const Video: FC<OwnProps> = ({
{hasThumb && !isPreviewPreloaded && ( {hasThumb && !isPreviewPreloaded && (
<canvas <canvas
ref={thumbRef} ref={thumbRef}
className={buildClassName('thumbnail', thumbClassNames)} className={buildClassName('thumbnail', !noThumb && 'canvas-blur-setup', thumbClassNames)}
/> />
)} )}
{isProtected && <span className="protector" />} {isProtected && <span className="protector" />}

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../../lib/teact/teact';
import React, { memo } from '../../../../lib/teact/teact'; import React, { memo } from '../../../../lib/teact/teact';
import type { import type {
@ -22,25 +21,29 @@ import styles from './ReactionButton.module.scss';
const REACTION_SIZE = 1.25 * REM; const REACTION_SIZE = 1.25 * REM;
const ReactionButton: FC<{ type OwnProps = {
reaction: ApiReactionCount; reaction: ApiReactionCount;
containerId: string; containerId: string;
isOwnMessage?: boolean; isOwnMessage?: boolean;
recentReactors?: ApiPeer[]; recentReactors?: ApiPeer[];
className?: string; className?: string;
chosenClassName?: string; chosenClassName?: string;
shouldDelayInit?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
onClick?: (reaction: ApiReaction) => void; onClick?: (reaction: ApiReaction) => void;
}> = ({ };
const ReactionButton = ({
reaction, reaction,
containerId, containerId,
isOwnMessage, isOwnMessage,
recentReactors, recentReactors,
className, className,
chosenClassName, chosenClassName,
shouldDelayInit,
observeIntersection, observeIntersection,
onClick, onClick,
}) => { }: OwnProps) => {
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
onClick?.(reaction.reaction); onClick?.(reaction.reaction);
}); });
@ -63,6 +66,7 @@ const ReactionButton: FC<{
reaction={reaction.reaction} reaction={reaction.reaction}
size={REACTION_SIZE} size={REACTION_SIZE}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
shouldDelayInit={shouldDelayInit}
/> />
{recentReactors?.length ? ( {recentReactors?.length ? (
<AvatarList size="mini" peers={recentReactors} /> <AvatarList size="mini" peers={recentReactors} />

View File

@ -10,12 +10,14 @@ import type {
ApiSavedReactionTag, ApiSavedReactionTag,
} from '../../../../api/types'; } from '../../../../api/types';
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
import type { Signal } from '../../../../util/signals';
import { getReactionKey, isReactionChosen } from '../../../../global/helpers'; import { getReactionKey, isReactionChosen } from '../../../../global/helpers';
import { selectPeer } from '../../../../global/selectors'; import { selectPeer } from '../../../../global/selectors';
import buildClassName from '../../../../util/buildClassName'; import buildClassName from '../../../../util/buildClassName';
import { getMessageKey } from '../../../../util/messageKey'; import { getMessageKey } from '../../../../util/messageKey';
import useDerivedState from '../../../../hooks/useDerivedState';
import useLang from '../../../../hooks/useLang'; import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback'; import useLastCallback from '../../../../hooks/useLastCallback';
@ -33,6 +35,7 @@ type OwnProps = {
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
noRecentReactors?: boolean; noRecentReactors?: boolean;
getIsMessageListReady: Signal<boolean>;
}; };
const MAX_RECENT_AVATARS = 3; const MAX_RECENT_AVATARS = 3;
@ -46,6 +49,7 @@ const Reactions: FC<OwnProps> = ({
noRecentReactors, noRecentReactors,
isCurrentUserPremium, isCurrentUserPremium,
tags, tags,
getIsMessageListReady,
}) => { }) => {
const { const {
toggleReaction, toggleReaction,
@ -61,6 +65,8 @@ const Reactions: FC<OwnProps> = ({
results.reduce((acc, reaction) => acc + reaction.count, 0) results.reduce((acc, reaction) => acc + reaction.count, 0)
), [results]); ), [results]);
const isMessageListReady = useDerivedState(getIsMessageListReady);
const recentReactorsByReactionKey = useMemo(() => { const recentReactorsByReactionKey = useMemo(() => {
const global = getGlobal(); const global = getGlobal();
@ -149,6 +155,7 @@ const Reactions: FC<OwnProps> = ({
onClick={handleClick} onClick={handleClick}
onRemove={handleRemoveReaction} onRemove={handleRemoveReaction}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
shouldDelayInit={!isMessageListReady}
/> />
) : ( ) : (
<ReactionButton <ReactionButton
@ -161,6 +168,7 @@ const Reactions: FC<OwnProps> = ({
reaction={reaction} reaction={reaction}
onClick={handleClick} onClick={handleClick}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
shouldDelayInit={!isMessageListReady}
/> />
) )
))} ))}

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../../lib/teact/teact';
import React, { memo, useRef } from '../../../../lib/teact/teact'; import React, { memo, useRef } from '../../../../lib/teact/teact';
import { getActions } from '../../../../global'; import { getActions } from '../../../../global';
@ -28,7 +27,7 @@ const REACTION_SIZE = 1.25 * REM;
const TITLE_MAX_LENGTH = 15; const TITLE_MAX_LENGTH = 15;
const LOOP_LIMIT = 1; const LOOP_LIMIT = 1;
const SavedTagButton: FC<{ type OwnProps = {
reaction: ApiReaction; reaction: ApiReaction;
tag?: ApiSavedReactionTag; tag?: ApiSavedReactionTag;
containerId: string; containerId: string;
@ -39,10 +38,13 @@ const SavedTagButton: FC<{
chosenClassName?: string; chosenClassName?: string;
isDisabled?: boolean; isDisabled?: boolean;
withContextMenu?: boolean; withContextMenu?: boolean;
shouldDelayInit?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
onClick?: (reaction: ApiReaction) => void; onClick?: (reaction: ApiReaction) => void;
onRemove?: (reaction: ApiReaction) => void; onRemove?: (reaction: ApiReaction) => void;
}> = ({ };
const SavedTagButton = ({
reaction, reaction,
tag, tag,
containerId, containerId,
@ -53,10 +55,11 @@ const SavedTagButton: FC<{
withCount, withCount,
isDisabled, isDisabled,
withContextMenu, withContextMenu,
shouldDelayInit,
observeIntersection, observeIntersection,
onClick, onClick,
onRemove, onRemove,
}) => { }: OwnProps) => {
const { editSavedReactionTag } = getActions(); const { editSavedReactionTag } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLButtonElement>(null); const ref = useRef<HTMLButtonElement>(null);
@ -138,6 +141,7 @@ const SavedTagButton: FC<{
loopLimit={LOOP_LIMIT} loopLimit={LOOP_LIMIT}
size={REACTION_SIZE} size={REACTION_SIZE}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
shouldDelayInit={shouldDelayInit}
/> />
{hasText && ( {hasText && (
<span className={styles.tagText}> <span className={styles.tagText}>

View File

@ -52,6 +52,8 @@ export default function useCanvasBlur(
ctx.drawImage(img, -radius * 2, -radius * 2, width + radius * 4, height + radius * 4); ctx.drawImage(img, -radius * 2, -radius * 2, width + radius * 4, height + radius * 4);
canvas.classList.remove('canvas-blur-setup');
if (!IS_CANVAS_FILTER_SUPPORTED) { if (!IS_CANVAS_FILTER_SUPPORTED) {
fastBlur(ctx, 0, 0, width, height, radius, ITERATIONS); fastBlur(ctx, 0, 0, width, height, radius, ITERATIONS);
} }

View File

@ -8,9 +8,9 @@
-webkit-user-select: none; -webkit-user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
&.lovely-chart--state-invisible > * { // &.lovely-chart--state-invisible > * {
display: none; // display: none;
} // }
> canvas, > canvas,
.lovely-chart--tooltip canvas { .lovely-chart--tooltip canvas {

View File

@ -201,6 +201,10 @@ body:not(.is-ios) {
} }
} }
.canvas-blur-setup {
will-change: width, height;
}
.emoji-small { .emoji-small {
background: no-repeat; background: no-repeat;
background-size: var(--emoji-size); background-size: var(--emoji-size);