diff --git a/web/src/shared/ui/artwork.tsx b/web/src/shared/ui/artwork.tsx index bf2249c..ea2dd26 100644 --- a/web/src/shared/ui/artwork.tsx +++ b/web/src/shared/ui/artwork.tsx @@ -3,10 +3,11 @@ import { useState } from 'react' export function Artwork({ src, title, className = '' }: { src?: string, title: string, className?: string }) { const [failedSrc, setFailedSrc] = useState() + const [loadedSrc, setLoadedSrc] = useState() return (
{src && src !== failedSrc - ? {title} setFailedSrc(src)} /> + ? {title} setLoadedSrc(src)} onError={() => setFailedSrc(src)} /> :
) diff --git a/web/src/shared/ui/ui.css b/web/src/shared/ui/ui.css index 5776b66..0d1bed1 100644 --- a/web/src/shared/ui/ui.css +++ b/web/src/shared/ui/ui.css @@ -33,7 +33,8 @@ .skeleton-row > div > span { display: block; width: 55%; height: 12px; border-radius: 4px; background: var(--color-surface-hover); } .skeleton-row > div > span + span { width: 35%; height: 10px; margin-top: 10px; background: var(--color-surface); } .artwork { flex-shrink: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--color-surface); color: var(--color-foreground-subtlest); border-radius: 8px; aspect-ratio: 1; } -.artwork img { width: 100%; height: 100%; object-fit: cover; } +.artwork img { width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 240ms ease; } +.artwork img[data-loaded] { opacity: 1; } .avatar { display: inline-grid; place-items: center; flex-shrink: 0; width: 32px; aspect-ratio: 1; overflow: hidden; border-radius: 50%; background: var(--color-accent); color: var(--color-brand); font-weight: 600; line-height: 1; } .avatar img { width: 100%; height: 100%; object-fit: cover; } .song-artwork { display: block; position: relative; flex-shrink: 0; border-radius: 8px; }