[Perf] Animated Emoji: Dispatch heavy animation event, better interaction animation

This commit is contained in:
Alexander Zinchuk 2022-01-24 04:41:57 +01:00
parent 42745ea024
commit b6c80b5236
7 changed files with 40 additions and 55 deletions

View File

@ -1,4 +1,5 @@
.AnimatedEmoji { .AnimatedEmoji {
cursor: pointer;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
img { img {

View File

@ -93,6 +93,7 @@ const AnimatedEmoji: FC<OwnProps> = ({
size={width} size={width}
quality={QUALITY} quality={QUALITY}
play={isIntersecting && playKey} play={isIntersecting && playKey}
forceOnHeavyAnimation
noLoop noLoop
onLoad={markAnimationLoaded} onLoad={markAnimationLoaded}
/> />

View File

@ -19,6 +19,7 @@ type OwnProps = {
quality?: number; quality?: number;
isLowPriority?: boolean; isLowPriority?: boolean;
onLoad?: NoneToVoidFunction; onLoad?: NoneToVoidFunction;
forceOnHeavyAnimation?: boolean;
color?: [number, number, number]; color?: [number, number, number];
onEnded?: NoneToVoidFunction; onEnded?: NoneToVoidFunction;
}; };
@ -53,8 +54,9 @@ const AnimatedSticker: FC<OwnProps> = ({
size, size,
quality, quality,
isLowPriority, isLowPriority,
onLoad,
color, color,
forceOnHeavyAnimation,
onLoad,
onEnded, onEnded,
}) => { }) => {
const [animation, setAnimation] = useState<RLottieInstance>(); const [animation, setAnimation] = useState<RLottieInstance>();
@ -207,7 +209,7 @@ const AnimatedSticker: FC<OwnProps> = ({
} }
}, [playAnimation, animation, animationData]); }, [playAnimation, animation, animationData]);
useHeavyAnimationCheck(freezeAnimation, unfreezeAnimation); useHeavyAnimationCheck(freezeAnimation, unfreezeAnimation, forceOnHeavyAnimation);
// 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,
// then we can play again. // then we can play again.

View File

@ -74,6 +74,7 @@ const LocalAnimatedEmoji: FC<OwnProps> = ({
size={width} size={width}
quality={QUALITY} quality={QUALITY}
play={isIntersecting && playKey} play={isIntersecting && playKey}
forceOnHeavyAnimation
noLoop noLoop
onLoad={markAnimationLoaded} onLoad={markAnimationLoaded}
/> />

View File

@ -2,7 +2,6 @@
--start-x: 0; --start-x: 0;
--start-y: 0; --start-y: 0;
--scale: 0; --scale: 0;
--end-scale: 0;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -11,42 +10,7 @@
pointer-events: none; pointer-events: none;
z-index: 1000; z-index: 1000;
@keyframes hide-reaction-reversed { @keyframes show-interaction {
from {
transform: translate(100%, -50%) scaleX(-1) scale(1);
}
to {
left: var(--end-x, var(--start-x));
top: var(--end-y, var(--start-y));
transform: translate(50%, 0) scale(var(--end-scale, 0));
}
}
@keyframes show-reaction-reversed {
from {
transform: translate(50%, 0) scaleX(-1) scale(var(--scale, 0));
}
to {
transform: translate(100%, -50%) scaleX(-1) scale(1);
}
}
@keyframes hide-reaction {
from {
transform: translate(-50%, -50%) scale(1);
}
to {
left: var(--end-x, var(--start-x));
top: var(--end-y, var(--start-y));
transform: translate(0, 0) scale(var(--end-scale, 0));
}
}
@keyframes show-reaction {
from { from {
transform: translate(0, 0) scale(var(--scale, 0)); transform: translate(0, 0) scale(var(--scale, 0));
} }
@ -56,13 +20,22 @@
} }
} }
@keyframes show-interaction-reversed {
from {
transform: translate(50%, 0) scaleX(-1) scale(var(--scale, 0));
}
to {
transform: translate(100%, -50%) scaleX(-1) scale(1);
}
}
.AnimatedSticker { .AnimatedSticker {
position: absolute; position: absolute;
top: var(--start-y); top: var(--start-y);
left: var(--start-x); left: var(--start-x);
transform: scale(var(--scale), 0); transform: scale(var(--scale), 0);
transform-origin: left top; transform-origin: left top;
transition: 0.25s cubic-bezier(.29,.81,.27,.99) opacity; transition: 0.25s cubic-bezier(.29,.81,.27,.99) opacity;
} }
@ -71,19 +44,18 @@
} }
&.playing .AnimatedSticker { &.playing .AnimatedSticker {
animation: show-reaction forwards 0.25s cubic-bezier(.29,.81,.27,.99); animation: show-interaction forwards 0.25s cubic-bezier(.29,.81,.27,.99);
} }
&.reversed.playing .AnimatedSticker { &.reversed.playing .AnimatedSticker {
animation: show-reaction-reversed forwards 0.25s cubic-bezier(.29,.81,.27,.99); animation: show-interaction-reversed forwards 0.25s cubic-bezier(.29,.81,.27,.99);
} }
&.hiding .AnimatedSticker { &.hiding .AnimatedSticker {
opacity: 0; opacity: 0;
animation: hide-reaction forwards 0.25s cubic-bezier(.29,.81,.27,.99);
} }
&.reversed.hiding .AnimatedSticker { &.reversed.hiding .AnimatedSticker {
animation: hide-reaction-reversed forwards 0.25s cubic-bezier(.29,.81,.27,.99); opacity: 0;
} }
} }

View File

@ -1,19 +1,20 @@
import React, { import React, {
FC, memo, useCallback, useEffect, useState, FC, memo, useCallback, useEffect, useLayoutEffect, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getDispatch, withGlobal } from '../../lib/teact/teactn'; import { getDispatch, withGlobal } from '../../lib/teact/teactn';
import { ActiveEmojiInteraction } from '../../global/types'; import { ActiveEmojiInteraction } from '../../global/types';
import { ApiMediaFormat } from '../../api/types'; import { ApiMediaFormat } from '../../api/types';
import { IS_ANDROID } from '../../util/environment';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useMedia from '../../hooks/useMedia'; import useMedia from '../../hooks/useMedia';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { import {
selectAnimatedEmojiEffect, selectAnimatedEmojiEffect,
} from '../../modules/selectors'; } from '../../modules/selectors';
import { REM } from '../common/helpers/mediaDimensions';
import getAnimationData, { ANIMATED_STICKERS_PATHS } from '../common/helpers/animatedAssets'; import getAnimationData, { ANIMATED_STICKERS_PATHS } from '../common/helpers/animatedAssets';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import AnimatedSticker from '../common/AnimatedSticker'; import AnimatedSticker from '../common/AnimatedSticker';
@ -31,7 +32,6 @@ type StateProps = {
const HIDE_ANIMATION_DURATION = 250; const HIDE_ANIMATION_DURATION = 250;
const PLAYING_DURATION = 3000; const PLAYING_DURATION = 3000;
const END_SIZE = 1.125 * REM;
const EFFECT_SIZE = 240; const EFFECT_SIZE = 240;
const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
@ -66,8 +66,13 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
}; };
}, [stop]); }, [stop]);
useEffect(() => { useLayoutEffect(() => {
setTimeout(stop, PLAYING_DURATION); const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
setTimeout(() => {
stop();
dispatchHeavyAnimationStop();
}, PLAYING_DURATION);
}, [stop]); }, [stop]);
const mediaDataEffect = useMedia(`sticker${effectAnimationId}`, !effectAnimationId, ApiMediaFormat.Lottie); const mediaDataEffect = useMedia(`sticker${effectAnimationId}`, !effectAnimationId, ApiMediaFormat.Lottie);
@ -82,7 +87,6 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
}, [localEffectAnimation]); }, [localEffectAnimation]);
const scale = (emojiInteraction.startSize || 0) / EFFECT_SIZE; const scale = (emojiInteraction.startSize || 0) / EFFECT_SIZE;
const endScale = END_SIZE / EFFECT_SIZE;
return ( return (
<div <div
@ -90,16 +94,15 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
'EmojiInteractionAnimation', isHiding && 'hiding', isPlaying && 'playing', isReversed && 'reversed', 'EmojiInteractionAnimation', isHiding && 'hiding', isPlaying && 'playing', isReversed && 'reversed',
)} )}
// @ts-ignore teact feature // @ts-ignore teact feature
style={`--end-scale: ${endScale}; --scale: ${scale}; --start-x: ${emojiInteraction.x}px;` style={`--scale: ${scale}; --start-x: ${emojiInteraction.x}px; --start-y: ${emojiInteraction.y}px;`}
+ `--start-y: ${emojiInteraction.y}px;${
emojiInteraction.endX
&& emojiInteraction.endY ? `--end-x: ${emojiInteraction.endX}px; --end-y: ${emojiInteraction.endY}px;` : ''}`}
> >
<AnimatedSticker <AnimatedSticker
id={`effect_${effectAnimationId}`} id={`effect_${effectAnimationId}`}
size={EFFECT_SIZE} size={EFFECT_SIZE}
animationData={(localEffectAnimationData || mediaDataEffect) as AnyLiteral} animationData={(localEffectAnimationData || mediaDataEffect) as AnyLiteral}
play={isPlaying} play={isPlaying}
isLowPriority={IS_ANDROID}
forceOnHeavyAnimation
noLoop noLoop
onLoad={startPlaying} onLoad={startPlaying}
/> />

View File

@ -12,8 +12,13 @@ const AUTO_END_TIMEOUT = 1000;
export default ( export default (
handleAnimationStart: AnyToVoidFunction, handleAnimationStart: AnyToVoidFunction,
handleAnimationEnd: AnyToVoidFunction, handleAnimationEnd: AnyToVoidFunction,
isDisabled = false,
) => { ) => {
useEffect(() => { useEffect(() => {
if (isDisabled) {
return undefined;
}
if (isAnimating) { if (isAnimating) {
handleAnimationStart(); handleAnimationStart();
} }
@ -25,7 +30,7 @@ export default (
document.removeEventListener(ANIMATION_END_EVENT, handleAnimationEnd); document.removeEventListener(ANIMATION_END_EVENT, handleAnimationEnd);
document.removeEventListener(ANIMATION_START_EVENT, handleAnimationStart); document.removeEventListener(ANIMATION_START_EVENT, handleAnimationStart);
}; };
}, [handleAnimationEnd, handleAnimationStart]); }, [isDisabled, handleAnimationEnd, handleAnimationStart]);
}; };
export function isHeavyAnimating() { export function isHeavyAnimating() {