From 589a01a2da52c3718c8f8e462c3bfee68d02ef43 Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 24 Aug 2026 12:46:08 +0200 Subject: [PATCH] fix(app): load expression grid images only when they scroll into view (#1922) --- .../emoji/components/emojis/EmojiListItem.tsx | 25 +++++----- .../components/stickers/StickerGridItem.tsx | 47 +++++++++++++------ 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/fluxer_app/src/features/emoji/components/emojis/EmojiListItem.tsx b/fluxer_app/src/features/emoji/components/emojis/EmojiListItem.tsx index cc02fca8f..32733d2af 100644 --- a/fluxer_app/src/features/emoji/components/emojis/EmojiListItem.tsx +++ b/fluxer_app/src/features/emoji/components/emojis/EmojiListItem.tsx @@ -3,10 +3,11 @@ import i18nGlobal from '@app/app/I18n'; import {GenericErrorModal} from '@app/features/app/components/alerts/GenericErrorModal'; import {ConfirmModal} from '@app/features/app/components/dialogs/ConfirmModal'; +import {useShouldAnimate} from '@app/features/app/hooks/useShouldAnimate'; import styles from '@app/features/emoji/components/emojis/EmojiListItem.module.css'; -import {useStickerAnimation} from '@app/features/emoji/hooks/useStickerAnimation'; import * as GuildEmojiCommands from '@app/features/expressions/commands/GuildEmojiCommands'; import Guilds from '@app/features/guild/state/Guilds'; +import {getEmojiRenderUrl} from '@app/features/messaging/utils/markdown/EmojiDetector'; import {Logger} from '@app/features/platform/utils/AppLogger'; import {failureCode} from '@app/features/platform/utils/ResponseInspection'; import {EmojiContextMenuItems} from '@app/features/ui/action_menu/items/EmojiContextMenuItems'; @@ -419,8 +420,15 @@ export const EmojiListItem: React.FC<{ )), ); }; - const {shouldAnimate} = useStickerAnimation(); - const emojiUrl = AvatarUtils.getEmojiURL({id: emoji.id, animated: shouldAnimate}); + const shouldAnimate = useShouldAnimate({kind: 'emoji', isAnimated: emoji.animated}); + const emojiUrl = + getEmojiRenderUrl({ + id: emoji.id, + surrogateUrl: null, + isAnimatable: emoji.animated, + animated: shouldAnimate, + jumbo: false, + }) ?? ''; const emojiForMenu = { id: emoji.id, guildId, @@ -457,7 +465,6 @@ export const EmojiListItem: React.FC<{ src={emojiUrl} alt={emoji.name} className={styles.gridEmojiImage} - loading="lazy" data-flx="emoji.emojis.emoji-list-item.grid-emoji-image" /> {emoji.user && avatarUrl && ( @@ -466,7 +473,6 @@ export const EmojiListItem: React.FC<{ src={avatarUrl} alt="" className={styles.gridAvatar} - loading="lazy" data-flx="emoji.emojis.emoji-list-item.grid-avatar" /> @@ -543,7 +549,6 @@ export const EmojiListItem: React.FC<{ src={emojiUrl} alt={emoji.name} className={styles.listEmojiImage} - loading="lazy" data-flx="emoji.emojis.emoji-list-item.list-emoji-image" /> @@ -570,13 +575,7 @@ export const EmojiListItem: React.FC<{
{emoji.user && avatarUrl ? ( <> - + {NicknameUtils.getDisplayName(emoji.user)} diff --git a/fluxer_app/src/features/emoji/components/stickers/StickerGridItem.tsx b/fluxer_app/src/features/emoji/components/stickers/StickerGridItem.tsx index d3f01f3b5..ec3c4f4dd 100644 --- a/fluxer_app/src/features/emoji/components/stickers/StickerGridItem.tsx +++ b/fluxer_app/src/features/emoji/components/stickers/StickerGridItem.tsx @@ -6,6 +6,7 @@ import {useStickerAnimation} from '@app/features/emoji/hooks/useStickerAnimation import * as GuildStickerCommands from '@app/features/expressions/commands/GuildStickerCommands'; import {EditGuildStickerModal} from '@app/features/expressions/components/modals/EditGuildStickerModal'; import Guilds from '@app/features/guild/state/Guilds'; +import {useNearViewport} from '@app/features/messaging/hooks/useNearViewport'; import {StickerContextMenuItems} from '@app/features/ui/action_menu/items/StickerContextMenuItems'; import {Checkbox} from '@app/features/ui/checkbox/Checkbox'; import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuCommands'; @@ -66,7 +67,10 @@ export const StickerGridItem = observer(function StickerGridItem({ onUpdate, }: StickerGridItemProps) { const {i18n} = useLingui(); - const {shouldAnimate} = useStickerAnimation(); + const {shouldAnimate, interactionHandlers} = useStickerAnimation({isAnimated: sticker.animated}); + const {ref: tileRef, isNearViewport} = useNearViewport({ + rememberKey: `guild-sticker-tile:${sticker.id}`, + }); const stickerName = sticker.name; const guild = Guilds.getGuild(guildId); const canExpressionPurge = guild?.features.has(GuildFeatures.EXPRESSION_PURGE_ALLOWED) ?? false; @@ -109,6 +113,7 @@ export const StickerGridItem = observer(function StickerGridItem({ const stickerUrl = AvatarUtils.getStickerURL({ id: sticker.id, animated: shouldAnimate, + isAnimatable: sticker.animated, size: 320, }); const avatarUrl = sticker.user ? AvatarUtils.getUserAvatarURL(sticker.user, false) : null; @@ -136,19 +141,25 @@ export const StickerGridItem = observer(function StickerGridItem({ }; return (
- {stickerName} + {isNearViewport && ( + {stickerName} + )}
@@ -158,13 +169,19 @@ export const StickerGridItem = observer(function StickerGridItem({
{sticker.user && avatarUrl && (
- + {isNearViewport ? ( + + ) : ( +
+ )} {NicknameUtils.getDisplayName(sticker.user)}