Message List: Optimizations (#4595)
This commit is contained in:
parent
1978419e84
commit
c04014b7a1
@ -16,6 +16,7 @@ import { IS_ELECTRON } from '../../util/windowEnvironment';
|
||||
|
||||
import useColorFilter from '../../hooks/stickers/useColorFilter';
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
|
||||
@ -96,6 +97,16 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|
||||
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(() => {
|
||||
if (color && !shouldUseColorFilter) {
|
||||
const { r, g, b } = hexToRgb(color);
|
||||
@ -118,6 +129,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|| isUnmountedRef.current
|
||||
|| !tgsUrl
|
||||
|| (sharedCanvas && (!sharedCanvasCoords || !sharedCanvas.offsetWidth || !sharedCanvas.offsetHeight))
|
||||
|| (isHeavyAnimating() && !shouldForceOnHeavyAnimation)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -154,12 +166,13 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!canInitialize) return;
|
||||
if (getRLottie()) {
|
||||
init();
|
||||
} else {
|
||||
ensureRLottie().then(init);
|
||||
}
|
||||
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords]);
|
||||
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords, canInitialize]);
|
||||
|
||||
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
|
||||
useSharedIntersectionObserver(sharedCanvas, throttledInit);
|
||||
@ -239,7 +252,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
}
|
||||
}, [playAnimation, animation, tgsUrl]);
|
||||
|
||||
useHeavyAnimationCheck(pauseAnimation, playAnimation, !playKey || forceAlways || forceOnHeavyAnimation);
|
||||
useHeavyAnimationCheck(pauseAnimation, playAnimation, !playKey || shouldForceOnHeavyAnimation);
|
||||
usePriorityPlaybackCheck(pauseAnimation, playAnimation, !playKey || forceAlways);
|
||||
// Pausing frame may not happen in background,
|
||||
// so we need to make sure it happens right after focusing,
|
||||
|
||||
@ -120,7 +120,7 @@ const File: FC<OwnProps> = ({
|
||||
{withThumb && (
|
||||
<canvas
|
||||
ref={thumbRef}
|
||||
className={buildClassName('thumbnail', thumbClassNames)}
|
||||
className={buildClassName('thumbnail', 'canvas-blur-setup', thumbClassNames)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -154,7 +154,7 @@ const GifButton: FC<OwnProps> = ({
|
||||
{withThumb && (
|
||||
<canvas
|
||||
ref={thumbRef}
|
||||
className="thumbnail"
|
||||
className="thumbnail canvas-blur-setup"
|
||||
// We need to always render to avoid blur re-calculation
|
||||
style={isVideoReady ? 'display: none;' : undefined}
|
||||
/>
|
||||
|
||||
@ -57,7 +57,12 @@ const MediaSpoiler: FC<OwnProps> = ({
|
||||
ref={ref}
|
||||
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>
|
||||
);
|
||||
|
||||
@ -119,7 +119,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
content = (
|
||||
<>
|
||||
{isBlurredThumb ? (
|
||||
<canvas ref={blurredThumbCanvasRef} className="thumb" />
|
||||
<canvas ref={blurredThumbCanvasRef} className="thumb canvas-blur-setup" />
|
||||
) : (
|
||||
<img src={avatarBlobUrl} draggable={false} className="thumb" alt="" />
|
||||
)}
|
||||
|
||||
@ -9,7 +9,7 @@ import { getStickerPreviewHash } from '../../global/helpers';
|
||||
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
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 useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
||||
@ -115,8 +115,8 @@ const StickerView: FC<OwnProps> = ({
|
||||
const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia);
|
||||
// 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));
|
||||
// Delay mounting on Android until heavy animation ends
|
||||
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!IS_ANDROID || !isHeavyAnimating());
|
||||
// Delay mounting until heavy animation ends
|
||||
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!isHeavyAnimating());
|
||||
useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount);
|
||||
const isFullMediaReady = isReadyToMount && fullMediaData && (isStatic || isPlayerReady);
|
||||
|
||||
|
||||
@ -9,6 +9,9 @@ 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 useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
@ -76,6 +79,10 @@ 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);
|
||||
@ -83,10 +90,10 @@ const EmojiIconBackground = ({
|
||||
const customColor = useDynamicColorListener(containerRef);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previewUrl) return;
|
||||
if (!previewUrl || !canUpdate) return;
|
||||
|
||||
preloadImage(previewUrl).then(setEmojiImage);
|
||||
}, [previewUrl]);
|
||||
}, [previewUrl, canUpdate]);
|
||||
|
||||
const updateCanvas = useLastCallback(() => {
|
||||
const canvas = canvasRef.current;
|
||||
@ -122,13 +129,15 @@ const EmojiIconBackground = ({
|
||||
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();
|
||||
}, [emojiImage, lang.isRtl, customColor]);
|
||||
}, [emojiImage, lang.isRtl, customColor, canUpdate]);
|
||||
|
||||
const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
if (!canvas || isHeavyAnimating()) return;
|
||||
|
||||
canvas.width = parentWidth * 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;
|
||||
if (!container) return;
|
||||
if (!container || !canUpdate) return;
|
||||
|
||||
const { width, height } = container.getBoundingClientRect();
|
||||
|
||||
requestMutation(() => {
|
||||
updateCanvasSize(width, height);
|
||||
});
|
||||
}, [dpr]);
|
||||
}, [dpr, canUpdate]);
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, className)} ref={containerRef}>
|
||||
|
||||
@ -36,6 +36,7 @@ type OwnProps = {
|
||||
shouldPause?: boolean;
|
||||
shouldLoop?: boolean;
|
||||
loopLimit?: number;
|
||||
shouldDelayInit?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
};
|
||||
|
||||
@ -66,6 +67,7 @@ const ReactionAnimatedEmoji = ({
|
||||
shouldPause,
|
||||
shouldLoop,
|
||||
loopLimit,
|
||||
shouldDelayInit,
|
||||
observeIntersection,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { stopActiveReaction } = getActions();
|
||||
@ -167,7 +169,7 @@ const ReactionAnimatedEmoji = ({
|
||||
size={size}
|
||||
noPlay={shouldPause}
|
||||
loopLimit={loopLimit}
|
||||
forceAlways
|
||||
forceAlways={!shouldDelayInit}
|
||||
observeIntersectionForPlaying={observeIntersection}
|
||||
/>
|
||||
)}
|
||||
@ -179,7 +181,7 @@ const ReactionAnimatedEmoji = ({
|
||||
tgsUrl={mediaDataCenterIcon}
|
||||
play={isIntersecting && !shouldPause}
|
||||
noLoop={!shouldLoop}
|
||||
forceAlways
|
||||
forceAlways={!shouldDelayInit}
|
||||
onLoad={markAnimationLoaded}
|
||||
onEnded={unmarkAnimationLoaded}
|
||||
/>
|
||||
@ -193,7 +195,7 @@ const ReactionAnimatedEmoji = ({
|
||||
tgsUrl={mediaDataEffect}
|
||||
play={isIntersecting}
|
||||
noLoop
|
||||
forceAlways
|
||||
forceAlways={!shouldDelayInit}
|
||||
onEnded={handleEnded}
|
||||
/>
|
||||
{isCustom && !assignedEffectId && isIntersecting && (
|
||||
|
||||
@ -83,7 +83,9 @@ function BaseStory({
|
||||
className={fullClassName}
|
||||
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 && (
|
||||
<>
|
||||
<img
|
||||
|
||||
@ -972,6 +972,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
noRecentReactors={isChannel}
|
||||
tags={tags}
|
||||
isCurrentUserPremium={isPremium}
|
||||
getIsMessageListReady={getIsMessageListReady}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1475,6 +1476,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
observeIntersection={observeIntersectionForPlaying}
|
||||
noRecentReactors={isChannel}
|
||||
tags={tags}
|
||||
getIsMessageListReady={getIsMessageListReady}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -199,7 +199,9 @@ const Photo: FC<OwnProps> = ({
|
||||
style={style}
|
||||
onClick={isUploading ? undefined : handleClick}
|
||||
>
|
||||
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
|
||||
{withBlurredBackground && (
|
||||
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
|
||||
)}
|
||||
<img
|
||||
src={fullMediaData}
|
||||
className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')}
|
||||
@ -208,7 +210,10 @@ const Photo: FC<OwnProps> = ({
|
||||
draggable={!isProtected}
|
||||
/>
|
||||
{withThumb && (
|
||||
<canvas ref={thumbRef} className={buildClassName('thumbnail', thumbClassNames)} />
|
||||
<canvas
|
||||
ref={thumbRef}
|
||||
className={buildClassName('thumbnail', !noThumb && 'canvas-blur-setup', thumbClassNames)}
|
||||
/>
|
||||
)}
|
||||
{isProtected && <span className="protector" />}
|
||||
{shouldRenderSpinner && !shouldRenderDownloadButton && (
|
||||
|
||||
@ -260,7 +260,7 @@ const RoundVideo: FC<OwnProps> = ({
|
||||
{!shouldRenderSpoiler && (
|
||||
<canvas
|
||||
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`}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -211,7 +211,9 @@ const Video: FC<OwnProps> = ({
|
||||
style={style}
|
||||
onClick={isUploading ? undefined : handleClick}
|
||||
>
|
||||
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
|
||||
{withBlurredBackground && (
|
||||
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
|
||||
)}
|
||||
{isInline && (
|
||||
<OptimizedVideo
|
||||
ref={videoRef}
|
||||
@ -237,7 +239,7 @@ const Video: FC<OwnProps> = ({
|
||||
{hasThumb && !isPreviewPreloaded && (
|
||||
<canvas
|
||||
ref={thumbRef}
|
||||
className={buildClassName('thumbnail', thumbClassNames)}
|
||||
className={buildClassName('thumbnail', !noThumb && 'canvas-blur-setup', thumbClassNames)}
|
||||
/>
|
||||
)}
|
||||
{isProtected && <span className="protector" />}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../../lib/teact/teact';
|
||||
|
||||
import type {
|
||||
@ -22,25 +21,29 @@ import styles from './ReactionButton.module.scss';
|
||||
|
||||
const REACTION_SIZE = 1.25 * REM;
|
||||
|
||||
const ReactionButton: FC<{
|
||||
type OwnProps = {
|
||||
reaction: ApiReactionCount;
|
||||
containerId: string;
|
||||
isOwnMessage?: boolean;
|
||||
recentReactors?: ApiPeer[];
|
||||
className?: string;
|
||||
chosenClassName?: string;
|
||||
shouldDelayInit?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
onClick?: (reaction: ApiReaction) => void;
|
||||
}> = ({
|
||||
};
|
||||
|
||||
const ReactionButton = ({
|
||||
reaction,
|
||||
containerId,
|
||||
isOwnMessage,
|
||||
recentReactors,
|
||||
className,
|
||||
chosenClassName,
|
||||
shouldDelayInit,
|
||||
observeIntersection,
|
||||
onClick,
|
||||
}) => {
|
||||
}: OwnProps) => {
|
||||
const handleClick = useLastCallback(() => {
|
||||
onClick?.(reaction.reaction);
|
||||
});
|
||||
@ -63,6 +66,7 @@ const ReactionButton: FC<{
|
||||
reaction={reaction.reaction}
|
||||
size={REACTION_SIZE}
|
||||
observeIntersection={observeIntersection}
|
||||
shouldDelayInit={shouldDelayInit}
|
||||
/>
|
||||
{recentReactors?.length ? (
|
||||
<AvatarList size="mini" peers={recentReactors} />
|
||||
|
||||
@ -10,12 +10,14 @@ import type {
|
||||
ApiSavedReactionTag,
|
||||
} from '../../../../api/types';
|
||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
import type { Signal } from '../../../../util/signals';
|
||||
|
||||
import { getReactionKey, isReactionChosen } from '../../../../global/helpers';
|
||||
import { selectPeer } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { getMessageKey } from '../../../../util/messageKey';
|
||||
|
||||
import useDerivedState from '../../../../hooks/useDerivedState';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
@ -33,6 +35,7 @@ type OwnProps = {
|
||||
isCurrentUserPremium?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
noRecentReactors?: boolean;
|
||||
getIsMessageListReady: Signal<boolean>;
|
||||
};
|
||||
|
||||
const MAX_RECENT_AVATARS = 3;
|
||||
@ -46,6 +49,7 @@ const Reactions: FC<OwnProps> = ({
|
||||
noRecentReactors,
|
||||
isCurrentUserPremium,
|
||||
tags,
|
||||
getIsMessageListReady,
|
||||
}) => {
|
||||
const {
|
||||
toggleReaction,
|
||||
@ -61,6 +65,8 @@ const Reactions: FC<OwnProps> = ({
|
||||
results.reduce((acc, reaction) => acc + reaction.count, 0)
|
||||
), [results]);
|
||||
|
||||
const isMessageListReady = useDerivedState(getIsMessageListReady);
|
||||
|
||||
const recentReactorsByReactionKey = useMemo(() => {
|
||||
const global = getGlobal();
|
||||
|
||||
@ -149,6 +155,7 @@ const Reactions: FC<OwnProps> = ({
|
||||
onClick={handleClick}
|
||||
onRemove={handleRemoveReaction}
|
||||
observeIntersection={observeIntersection}
|
||||
shouldDelayInit={!isMessageListReady}
|
||||
/>
|
||||
) : (
|
||||
<ReactionButton
|
||||
@ -161,6 +168,7 @@ const Reactions: FC<OwnProps> = ({
|
||||
reaction={reaction}
|
||||
onClick={handleClick}
|
||||
observeIntersection={observeIntersection}
|
||||
shouldDelayInit={!isMessageListReady}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo, useRef } from '../../../../lib/teact/teact';
|
||||
import { getActions } from '../../../../global';
|
||||
|
||||
@ -28,7 +27,7 @@ const REACTION_SIZE = 1.25 * REM;
|
||||
const TITLE_MAX_LENGTH = 15;
|
||||
const LOOP_LIMIT = 1;
|
||||
|
||||
const SavedTagButton: FC<{
|
||||
type OwnProps = {
|
||||
reaction: ApiReaction;
|
||||
tag?: ApiSavedReactionTag;
|
||||
containerId: string;
|
||||
@ -39,10 +38,13 @@ const SavedTagButton: FC<{
|
||||
chosenClassName?: string;
|
||||
isDisabled?: boolean;
|
||||
withContextMenu?: boolean;
|
||||
shouldDelayInit?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
onClick?: (reaction: ApiReaction) => void;
|
||||
onRemove?: (reaction: ApiReaction) => void;
|
||||
}> = ({
|
||||
};
|
||||
|
||||
const SavedTagButton = ({
|
||||
reaction,
|
||||
tag,
|
||||
containerId,
|
||||
@ -53,10 +55,11 @@ const SavedTagButton: FC<{
|
||||
withCount,
|
||||
isDisabled,
|
||||
withContextMenu,
|
||||
shouldDelayInit,
|
||||
observeIntersection,
|
||||
onClick,
|
||||
onRemove,
|
||||
}) => {
|
||||
}: OwnProps) => {
|
||||
const { editSavedReactionTag } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
@ -138,6 +141,7 @@ const SavedTagButton: FC<{
|
||||
loopLimit={LOOP_LIMIT}
|
||||
size={REACTION_SIZE}
|
||||
observeIntersection={observeIntersection}
|
||||
shouldDelayInit={shouldDelayInit}
|
||||
/>
|
||||
{hasText && (
|
||||
<span className={styles.tagText}>
|
||||
|
||||
@ -52,6 +52,8 @@ export default function useCanvasBlur(
|
||||
|
||||
ctx.drawImage(img, -radius * 2, -radius * 2, width + radius * 4, height + radius * 4);
|
||||
|
||||
canvas.classList.remove('canvas-blur-setup');
|
||||
|
||||
if (!IS_CANVAS_FILTER_SUPPORTED) {
|
||||
fastBlur(ctx, 0, 0, width, height, radius, ITERATIONS);
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
-webkit-user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
&.lovely-chart--state-invisible > * {
|
||||
display: none;
|
||||
}
|
||||
// &.lovely-chart--state-invisible > * {
|
||||
// display: none;
|
||||
// }
|
||||
|
||||
> canvas,
|
||||
.lovely-chart--tooltip canvas {
|
||||
|
||||
@ -201,6 +201,10 @@ body:not(.is-ios) {
|
||||
}
|
||||
}
|
||||
|
||||
.canvas-blur-setup {
|
||||
will-change: width, height;
|
||||
}
|
||||
|
||||
.emoji-small {
|
||||
background: no-repeat;
|
||||
background-size: var(--emoji-size);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user