fix(app): load expression grid images only when they scroll into view (#1922)

This commit is contained in:
Hampus
2026-08-24 12:46:08 +02:00
committed by GitHub
parent aae6b99761
commit 589a01a2da
2 changed files with 44 additions and 28 deletions
@@ -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"
/>
</Tooltip>
@@ -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"
/>
</div>
@@ -570,13 +575,7 @@ export const EmojiListItem: React.FC<{
<div className={styles.listUploader} data-flx="emoji.emojis.emoji-list-item.list-uploader">
{emoji.user && avatarUrl ? (
<>
<img
src={avatarUrl}
alt=""
className={styles.avatar}
loading="lazy"
data-flx="emoji.emojis.emoji-list-item.avatar"
/>
<img src={avatarUrl} alt="" className={styles.avatar} data-flx="emoji.emojis.emoji-list-item.avatar" />
<span className={styles.username} data-flx="emoji.emojis.emoji-list-item.username">
{NicknameUtils.getDisplayName(emoji.user)}
</span>
@@ -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<HTMLDivElement>({
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 (
<div
ref={tileRef}
role="group"
className={styles.container}
onContextMenu={handleContextMenu}
onMouseEnter={interactionHandlers.onMouseEnter}
onMouseLeave={interactionHandlers.onMouseLeave}
onFocus={interactionHandlers.onFocus}
onBlur={interactionHandlers.onBlur}
data-flx="emoji.stickers.sticker-grid-item.container.context-menu"
>
<div className={styles.stickerWrapper} data-flx="emoji.stickers.sticker-grid-item.sticker-wrapper">
<img
src={stickerUrl}
alt={stickerName}
className={styles.stickerImage}
loading="lazy"
data-flx="emoji.stickers.sticker-grid-item.sticker-image"
/>
{isNearViewport && (
<img
src={stickerUrl}
alt={stickerName}
className={styles.stickerImage}
data-flx="emoji.stickers.sticker-grid-item.sticker-image"
/>
)}
</div>
<div className={styles.content} data-flx="emoji.stickers.sticker-grid-item.content">
<div className={styles.header} data-flx="emoji.stickers.sticker-grid-item.header">
@@ -158,13 +169,19 @@ export const StickerGridItem = observer(function StickerGridItem({
</div>
{sticker.user && avatarUrl && (
<div className={styles.authorInfo} data-flx="emoji.stickers.sticker-grid-item.author-info">
<img
src={avatarUrl}
alt=""
className={styles.authorAvatar}
loading="lazy"
data-flx="emoji.stickers.sticker-grid-item.author-avatar"
/>
{isNearViewport ? (
<img
src={avatarUrl}
alt=""
className={styles.authorAvatar}
data-flx="emoji.stickers.sticker-grid-item.author-avatar"
/>
) : (
<div
className={styles.authorAvatar}
data-flx="emoji.stickers.sticker-grid-item.author-avatar-placeholder"
/>
)}
<span className={styles.authorName} data-flx="emoji.stickers.sticker-grid-item.author-name">
{NicknameUtils.getDisplayName(sticker.user)}
</span>