Animate gifts on hover

This commit is contained in:
Alexander Zinchuk 2025-08-15 18:25:35 +02:00
parent 3d60271505
commit e77c6c40f8
9 changed files with 61 additions and 19 deletions

View File

@ -50,6 +50,8 @@ export type OwnProps = {
sharedCanvas?: HTMLCanvasElement;
sharedCanvasCoords?: { x: number; y: number };
onClick?: NoneToVoidFunction;
onMouseEnter?: NoneToVoidFunction;
onMouseLeave?: NoneToVoidFunction;
onLoad?: NoneToVoidFunction;
onEnded?: NoneToVoidFunction;
onLoop?: NoneToVoidFunction;
@ -76,6 +78,8 @@ const AnimatedSticker: FC<OwnProps> = ({
sharedCanvas,
sharedCanvasCoords,
onClick,
onMouseEnter,
onMouseLeave,
onLoad,
onEnded,
onLoop,
@ -277,6 +281,8 @@ const AnimatedSticker: FC<OwnProps> = ({
style,
)}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
};

View File

@ -6,6 +6,7 @@ import type { ApiEmojiStatusType, ApiPeer, ApiSavedStarGift } from '../../../api
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
import { getHasAdminRight } from '../../../global/helpers';
import { selectChat, selectPeer, selectUser } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment.ts';
import buildClassName from '../../../util/buildClassName';
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
import { CUSTOM_PEER_HIDDEN } from '../../../util/objects/customPeer';
@ -13,6 +14,7 @@ import { formatIntegerCompact } from '../../../util/textFormat';
import { getGiftAttributes, getStickerFromGift, getTotalGiftAvailability } from '../helpers/gifts';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useFlag from '../../../hooks/useFlag.ts';
import { type ObserveFn } from '../../../hooks/useIntersectionObserver';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
@ -59,13 +61,13 @@ const SavedGift = ({
const { openGiftInfoModal } = getActions();
const ref = useRef<HTMLDivElement>();
const stickerRef = useRef<HTMLDivElement>();
const lang = useLang();
const canManage = peerId === currentUserId || hasAdminRights;
const [isHover, markHover, unmarkHover] = useFlag();
const canManage = peerId === currentUserId || hasAdminRights;
const totalIssued = getTotalGiftAvailability(gift.gift);
const starGift = gift.gift;
const starGiftUnique = starGift.type === 'starGiftUnique' ? starGift : undefined;
@ -150,6 +152,8 @@ const SavedGift = ({
onClick={handleClick}
onContextMenu={handleContextMenu}
onMouseDown={handleBeforeContextMenu}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
>
{radialPatternBackdrop}
{!radialPatternBackdrop && <Avatar className={styles.topIcon} peer={avatarPeer} size="micro" />}
@ -161,12 +165,13 @@ const SavedGift = ({
>
{sticker && (
<StickerView
observeIntersectionForPlaying={observeIntersection}
observeIntersectionForLoading={observeIntersection}
containerRef={stickerRef}
sticker={sticker}
size={GIFT_STICKER_SIZE}
shouldLoop={isHover}
shouldPreloadPreview
observeIntersectionForPlaying={observeIntersection}
observeIntersectionForLoading={observeIntersection}
/>
)}

View File

@ -167,6 +167,7 @@
}
.starGift {
cursor: var(--custom-cursor, pointer);
width: 13.75rem;
}

View File

@ -1,9 +1,11 @@
import { memo, useRef } from '../../../lib/teact/teact';
import { memo, useRef } from '@teact';
import type { ApiStarGiftUnique } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment.ts';
import { getGiftAttributes } from '../../common/helpers/gifts';
import useFlag from '../../../hooks/useFlag.ts';
import { type ObserveFn } from '../../../hooks/useIntersectionObserver';
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
@ -31,12 +33,16 @@ const WebPageUniqueGift = ({
backdrop, model, pattern,
} = getGiftAttributes(gift)!;
const [isHover, markHover, unmarkHover] = useFlag();
const backgroundColors = [backdrop!.centerColor, backdrop!.edgeColor];
return (
<div
className={styles.root}
onClick={onClick}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
>
<div className={styles.backgroundWrapper}>
<RadialPatternBackground
@ -51,6 +57,7 @@ const WebPageUniqueGift = ({
containerRef={stickerRef}
sticker={model!.sticker}
size={STAR_GIFT_STICKER_SIZE}
shouldLoop={isHover}
observeIntersectionForPlaying={observeIntersectionForPlaying}
observeIntersectionForLoading={observeIntersectionForLoading}
/>

View File

@ -1,4 +1,4 @@
import { memo, useMemo, useRef } from '../../../../lib/teact/teact';
import { memo, useMemo, useRef } from '@teact';
import { withGlobal } from '../../../../global';
import type { ApiMessage, ApiPeer } from '../../../../api/types';
@ -12,6 +12,7 @@ import {
selectSender,
selectUser,
} from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment.ts';
import buildClassName from '../../../../util/buildClassName';
import { formatStarsAsText } from '../../../../util/localization/format';
import { getServerTime } from '../../../../util/serverTime';
@ -21,6 +22,7 @@ import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEn
import { renderPeerLink, translateWithYou } from '../helpers/messageActions';
import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener';
import useFlag from '../../../../hooks/useFlag.ts';
import { type ObserveFn } from '../../../../hooks/useIntersectionObserver';
import useLang from '../../../../hooks/useLang';
@ -62,6 +64,8 @@ const StarGiftAction = ({
const stickerRef = useRef<HTMLDivElement>();
const lang = useLang();
const [isHover, markHover, unmarkHover] = useFlag();
const { isOutgoing } = message;
const sticker = getStickerFromGift(action.gift)!;
@ -123,6 +127,8 @@ const StarGiftAction = ({
tabIndex={0}
role="button"
onClick={onClick}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
>
<div
ref={stickerRef}
@ -134,6 +140,7 @@ const StarGiftAction = ({
containerRef={stickerRef}
sticker={sticker}
size={STICKER_SIZE}
shouldLoop={isHover}
observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying}
noLoad={!canPlayAnimatedEmojis}

View File

@ -1,4 +1,4 @@
import { memo, useMemo, useRef } from '../../../../lib/teact/teact';
import { memo, useMemo, useRef } from '@teact';
import { withGlobal } from '../../../../global';
import type { ApiMessage, ApiPeer } from '../../../../api/types';
@ -11,11 +11,13 @@ import {
selectSender,
selectUser,
} from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment.ts';
import buildClassName from '../../../../util/buildClassName';
import buildStyle from '../../../../util/buildStyle';
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
import { renderPeerLink } from '../helpers/messageActions';
import useFlag from '../../../../hooks/useFlag.ts';
import { type ObserveFn } from '../../../../hooks/useIntersectionObserver';
import useLang from '../../../../hooks/useLang';
@ -56,6 +58,8 @@ const StarGiftAction = ({
const stickerRef = useRef<HTMLDivElement>();
const lang = useLang();
const [isHover, markHover, unmarkHover] = useFlag();
const { isOutgoing } = message;
const sticker = getStickerFromGift(action.gift)!;
@ -85,6 +89,8 @@ const StarGiftAction = ({
tabIndex={0}
role="button"
onClick={onClick}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
>
<div className={styles.uniqueBackgroundWrapper}>
<RadialPatternBackground
@ -105,6 +111,7 @@ const StarGiftAction = ({
containerRef={stickerRef}
sticker={sticker}
size={STICKER_SIZE}
shouldLoop={isHover}
observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying}
noLoad={!canPlayAnimatedEmojis}

View File

@ -1,18 +1,16 @@
import { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
import { memo, useMemo, useRef, useState } from '@teact';
import { getActions, withGlobal } from '../../../global';
import type {
ApiStarGift,
ApiTypeCurrencyAmount,
} from '../../../api/types';
import type { ApiStarGift, ApiTypeCurrencyAmount } from '../../../api/types';
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
import { selectIsCurrentUserPremium } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment.ts';
import buildClassName from '../../../util/buildClassName';
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
import { getStickerFromGift } from '../../common/helpers/gifts';
import { getGiftAttributes } from '../../common/helpers/gifts';
import { getGiftAttributes, getStickerFromGift } from '../../common/helpers/gifts';
import useFlag from '../../../hooks/useFlag.ts';
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
@ -58,7 +56,9 @@ function GiftItemStar({
}
const lang = useLang();
const [isVisible, setIsVisible] = useState(false);
const [isHover, markHover, unmarkHover] = useFlag();
const sticker = getStickerFromGift(gift);
const isGiftUnique = gift.type === 'starGiftUnique';
@ -175,6 +175,8 @@ function GiftItemStar({
tabIndex={0}
role="button"
onClick={handleGiftClick}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
>
{radialPatternBackdrop}
@ -190,6 +192,7 @@ function GiftItemStar({
containerRef={stickerRef}
sticker={sticker}
size={GIFT_STICKER_SIZE}
shouldLoop={isHover}
shouldPreloadPreview
/>
)}

View File

@ -1,8 +1,8 @@
import type { FC } from '../../../lib/teact/teact';
import type React from '../../../lib/teact/teact';
import type { FC } from '@teact';
import {
memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
} from '@teact';
import type React from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type {

View File

@ -1,5 +1,5 @@
import type { TeactNode } from '../../../lib/teact/teact';
import { memo, useMemo } from '../../../lib/teact/teact';
import type { TeactNode } from '@teact';
import { memo, useMemo } from '@teact';
import { getActions } from '../../../global';
import type {
@ -10,10 +10,12 @@ import type {
import {
formatStarsTransactionAmount,
} from '../../../global/helpers/payments';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment.ts';
import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import { useTransitionActiveKey } from '../../../hooks/animations/useTransitionActiveKey';
import useFlag from '../../../hooks/useFlag.ts';
import useLang from '../../../hooks/useLang';
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
@ -52,6 +54,7 @@ const UniqueGiftHeader = ({
} = getActions();
const lang = useLang();
const [isHover, markHover, unmarkHover] = useFlag();
const activeKey = useTransitionActiveKey([modelAttribute, backdropAttribute, patternAttribute]);
const subtitleColor = backdropAttribute?.textColor;
@ -83,6 +86,9 @@ const UniqueGiftHeader = ({
className={styles.sticker}
sticker={modelAttribute.sticker}
size={STICKER_SIZE}
noLoop={!isHover}
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
/>
</Transition>
{title && <h1 className={styles.title}>{title}</h1>}