mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-08 03:32:27 +09:00
fix(app): animate guild icons through the shared motion gate (#1931)
This commit is contained in:
@@ -17,7 +17,6 @@
|
||||
background-image: var(--guild-icon-image, none);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
transition: background-image 0.3s ease-in-out;
|
||||
container-type: inline-size;
|
||||
--initials-font-size: clamp(0.85rem, 45cqi, 1.35rem);
|
||||
--initials-letter-spacing: 0px;
|
||||
@@ -54,10 +53,6 @@
|
||||
--initials-letter-spacing: 0px;
|
||||
}
|
||||
|
||||
.container[data-reduced-motion='true'] {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@container (max-width: 32px) {
|
||||
.initials {
|
||||
--initials-scale-cap: 0.88;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import Accessibility from '@app/features/accessibility/state/Accessibility';
|
||||
import {useHover} from '@app/features/app/hooks/useHover';
|
||||
import {useShouldAnimate} from '@app/features/app/hooks/useShouldAnimate';
|
||||
import styles from '@app/features/guild/components/popouts/GuildIcon.module.css';
|
||||
import {getGuildIconDisplayInitials, getInitialsLength} from '@app/features/guild/utils/GuildInitialsUtils';
|
||||
import * as ImageCacheUtils from '@app/features/messaging/utils/ImageCacheUtils';
|
||||
@@ -30,12 +30,6 @@ type GuildIconStyleVars = React.CSSProperties & {
|
||||
'--guild-icon-image'?: string;
|
||||
};
|
||||
|
||||
function resolveInitiallyLoadedImageUrl(url: string | null): string | null {
|
||||
if (url == null) return null;
|
||||
if (ImageCacheUtils.hasImage(url)) return url;
|
||||
return null;
|
||||
}
|
||||
|
||||
export const GuildIcon = observer(function GuildIcon({
|
||||
id,
|
||||
name,
|
||||
@@ -48,57 +42,35 @@ export const GuildIcon = observer(function GuildIcon({
|
||||
const initials = useMemo(() => getGuildIconDisplayInitials(rawInitials), [rawInitials]);
|
||||
const initialsLength = useMemo(() => getInitialsLength(rawInitials), [rawInitials]);
|
||||
const [hoverRef, isHovering] = useHover();
|
||||
const iconUrl = useMemo(() => (icon ? AvatarUtils.getGuildIconURL({id, icon}) : null), [id, icon]);
|
||||
const hoverIconUrl = useMemo(() => (icon ? AvatarUtils.getGuildIconURL({id, icon}, true) : null), [id, icon]);
|
||||
const [loadedStaticUrl, setLoadedStaticUrl] = useState<string | null>(() => resolveInitiallyLoadedImageUrl(iconUrl));
|
||||
const [loadedAnimatedUrl, setLoadedAnimatedUrl] = useState<string | null>(() =>
|
||||
resolveInitiallyLoadedImageUrl(hoverIconUrl),
|
||||
);
|
||||
const isStaticLoaded = iconUrl != null && loadedStaticUrl === iconUrl;
|
||||
const iconUrl = icon ? AvatarUtils.getGuildIconURL({id, icon}) : null;
|
||||
const hoverIconUrl = icon ? AvatarUtils.getGuildIconURL({id, icon}, true) : null;
|
||||
const isAnimatable = hoverIconUrl != null && hoverIconUrl !== iconUrl;
|
||||
const animationAllowed = useShouldAnimate({kind: 'guild_icon', isAnimated: isAnimatable, isHovering});
|
||||
const [loadedAnimatedUrl, setLoadedAnimatedUrl] = useState<string | null>(null);
|
||||
const isAnimatedLoaded = hoverIconUrl != null && loadedAnimatedUrl === hoverIconUrl;
|
||||
useEffect(() => {
|
||||
if (iconUrl == null || iconUrl.length === 0 || isStaticLoaded) return;
|
||||
let active = true;
|
||||
const cleanup = ImageCacheUtils.loadImage(iconUrl, () => {
|
||||
if (active) setLoadedStaticUrl(iconUrl);
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
cleanup();
|
||||
};
|
||||
}, [iconUrl, isStaticLoaded]);
|
||||
useEffect(() => {
|
||||
if (!isHovering || hoverIconUrl == null || hoverIconUrl.length === 0 || isAnimatedLoaded) return;
|
||||
let active = true;
|
||||
const cleanup = ImageCacheUtils.loadImage(hoverIconUrl, () => {
|
||||
if (active) setLoadedAnimatedUrl(hoverIconUrl);
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
cleanup();
|
||||
};
|
||||
}, [isHovering, hoverIconUrl, isAnimatedLoaded]);
|
||||
const shouldPlayAnimated = isHovering && isAnimatedLoaded;
|
||||
const activeUrl = shouldPlayAnimated && hoverIconUrl != null ? hoverIconUrl : iconUrl;
|
||||
if (!animationAllowed || hoverIconUrl == null || isAnimatedLoaded) return;
|
||||
return ImageCacheUtils.loadImage(hoverIconUrl, () => setLoadedAnimatedUrl(hoverIconUrl));
|
||||
}, [animationAllowed, hoverIconUrl, isAnimatedLoaded]);
|
||||
const activeUrl = animationAllowed && isAnimatedLoaded ? hoverIconUrl : iconUrl;
|
||||
const paintedUrl = activeUrl != null && activeUrl.length > 0 ? activeUrl : null;
|
||||
const styleVars: GuildIconStyleVars = {};
|
||||
if (sizePx != null) {
|
||||
styleVars['--guild-icon-size'] = remFromPx(sizePx);
|
||||
}
|
||||
if (isStaticLoaded && activeUrl) {
|
||||
styleVars['--guild-icon-image'] = `url(${activeUrl})`;
|
||||
if (paintedUrl != null) {
|
||||
styleVars['--guild-icon-image'] = `url(${paintedUrl})`;
|
||||
}
|
||||
const reducedMotion = Accessibility.useReducedMotion;
|
||||
return (
|
||||
<div
|
||||
ref={hoverRef}
|
||||
className={clsx(styles.container, className, !icon && styles.containerNoIcon)}
|
||||
className={clsx(styles.container, className, paintedUrl == null && styles.containerNoIcon)}
|
||||
data-flx="guild.guild-icon.container"
|
||||
{...containerProps}
|
||||
data-initials-length={initialsLength}
|
||||
data-reduced-motion={reducedMotion}
|
||||
style={styleVars}
|
||||
>
|
||||
{!icon && (
|
||||
{paintedUrl == null && (
|
||||
<span className={styles.initials} data-flx="guild.guild-icon.initials">
|
||||
{initials}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user