feat(showcase): improve remote image loading with skeleton animation and fade-in effects

- Add preloadMedia() for async image/video loading before display
- Implement renderLoadingSkeleton() with fa-circle-notch fa-spin animation
- Add fadeIn transition (opacity 0→1) for main media elements
- Remove shimmer gradient animation from thumbnails for cleaner look
- Use solid background color placeholder with subtle fade-in for thumbnails
- Fixes progressive rendering of remote images from top to bottom
- Prevents black flash during loading with proper loading states
This commit is contained in:
Will Miao
2026-02-06 23:49:45 +08:00
parent 469f7a1829
commit 4d9115339b
3 changed files with 133 additions and 11 deletions

View File

@@ -77,7 +77,12 @@
max-height: 70vh;
object-fit: contain;
border-radius: var(--border-radius-sm);
transition: filter 0.2s ease;
transition: filter 0.2s ease, opacity 0.3s ease;
opacity: 0;
}
.showcase__media.loaded {
opacity: 1;
}
.showcase__media.blurred {
@@ -384,6 +389,51 @@
font-size: 2rem;
}
/* Skeleton loading state */
.showcase__skeleton {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--lora-surface);
}
.skeleton-animation {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-3);
color: var(--text-color);
opacity: 0.6;
}
.skeleton-spinner {
font-size: 2.5rem;
color: var(--lora-accent);
}
/* Error state */
.showcase__error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--lora-error);
gap: var(--space-2);
}
.showcase__error i {
font-size: 2rem;
}
.showcase__error p {
margin: 0;
font-size: 0.9em;
}
/* Empty state */
.showcase__empty {
display: flex;

View File

@@ -36,7 +36,19 @@
position: relative;
border: 2px solid transparent;
transition: border-color 0.2s ease, transform 0.2s ease;
background: var(--bg-color);
background: var(--lora-surface);
}
.thumbnail-rail__item img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 0.3s ease;
}
.thumbnail-rail__item img.loaded {
opacity: 1;
}
.thumbnail-rail__item:hover {
@@ -49,12 +61,6 @@
box-shadow: 0 0 0 2px oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.3);
}
.thumbnail-rail__item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* NSFW blur for thumbnails - BEM naming to avoid conflicts with global .nsfw-blur */
.thumbnail-rail__item--nsfw-blurred img {
filter: blur(8px);