Files
immich/web/src/lib/components/LoadingDots.svelte
Min Idzelis 8764a1894b feat: adaptive progressive image loading for photo viewer (#26636)
* feat(web): adaptive progressive image loading for photo viewer

Replace ImageManager with a new AdaptiveImageLoader that progressively
loads images through quality tiers (thumbnail → preview → original).

New components and utilities:
- AdaptiveImage: layered image renderer with thumbhash, thumbnail,
  preview, and original layers with visibility managed by load state
- AdaptiveImageLoader: state machine driving the quality progression
  with per-quality callbacks and error handling
- ImageLayer/Image: low-level image elements with load/error lifecycle
- PreloadManager: preloads adjacent assets for instant navigation
- AlphaBackground/DelayedLoadingSpinner: loading state UI

Zoom is handled via a derived CSS transform applied to the content
wrapper in AdaptiveImage, with the zoom library (zoomTarget: null)
only tracking state without manipulating the DOM directly.

Also adds scaleToCover to container-utils and getAssetUrls to utils.

* fix: don't partially render images in firefox

* add passive loading indicator to asset-viewer

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-03-11 09:48:46 -05:00

47 lines
884 B
Svelte

<script lang="ts">
import type { ClassValue } from 'svelte/elements';
interface Props {
class?: ClassValue;
}
let { class: className }: Props = $props();
</script>
<div class="delayed inline-flex items-center gap-1 {className}">
{#each [0, 1, 2] as i (i)}
<span class="dot block size-1.5 rounded-full bg-white shadow-[0_0_3px_rgba(0,0,0,0.6)]" style:--delay="{i * 0.25}s"
></span>
{/each}
</div>
<style>
.delayed {
visibility: hidden;
animation: delayed-visibility 0s linear 0.4s forwards;
}
@keyframes delayed-visibility {
to {
visibility: visible;
}
}
.dot {
animation: dot-stream 1.6s var(--delay, 0s) ease-in-out infinite;
}
@keyframes dot-stream {
0%,
80%,
100% {
opacity: 0.3;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: scale(1.15);
}
}
</style>