world-game/src/components/ui/GalleryAnimation.svelte

124 lines
3.3 KiB
Svelte
Raw Normal View History

<script>
/**
* GalleryAnimation — animation CSS de galerie en 3 colonnes défilantes.
* @prop {Array<{src: string, srcset: string, webp: string}>} images
* @prop {number} secondsPerImage — durée par image (défaut: 8s)
*/
let { images = [], secondsPerImage = 8 } = $props()
const columns = $derived.by(() => {
const count = images.length
const duration = count * secondsPerImage
const defs = [
{ offset: 0, delay: 0 },
{ offset: Math.floor(count / 3), delay: duration / 4 },
{ offset: 0, delay: duration / 2 },
]
return defs.map(({ offset, delay }) => ({
images: shiftImages(images, offset),
delay,
duration,
}))
})
function shiftImages(imgs, offset) {
if (!offset) return imgs
return [...imgs.slice(offset), ...imgs.slice(0, offset)]
}
</script>
<div
class="gallery-animation gallery-animation--vertical"
style="--gallery-duration: {columns[0]?.duration ?? 24}s"
>
{#each columns as col}
<div class="gallery-animation__column">
<div
class="gallery-animation__track"
style="animation-delay: -{col.delay}s"
>
<!-- Images × 2 pour le défilement infini -->
{#each [col.images, col.images] as set}
{#each set as img}
<picture>
<source type="image/webp" srcset={img.webp} sizes="(max-width: 700px) 33vw, 15vw" />
<img
class="gallery-animation__image"
src={img.src}
srcset={img.srcset}
sizes="(max-width: 700px) 33vw, 15vw"
alt=""
aria-hidden="true"
loading="lazy"
decoding="async"
/>
</picture>
{/each}
{/each}
</div>
</div>
{/each}
</div>
<style>
.gallery-animation {
width: 100%;
height: 100%;
overflow: hidden;
}
/* Vertical mode (Portfolio) */
.gallery-animation--vertical {
display: flex;
flex-direction: row;
gap: 8px;
}
.gallery-animation--vertical :global(.gallery-animation__column) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.gallery-animation--vertical :global(.gallery-animation__track) {
display: flex;
flex-direction: column;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.gallery-animation--vertical :global(.gallery-animation__column:nth-child(odd) .gallery-animation__track) {
animation-name: galleryScrollDown;
animation-duration: var(--gallery-duration);
}
.gallery-animation--vertical :global(.gallery-animation__column:nth-child(even) .gallery-animation__track) {
animation-name: galleryScrollUp;
animation-duration: var(--gallery-duration);
}
:global(.gallery-animation__image) {
width: 100%;
height: auto;
display: block;
object-fit: contain;
}
@keyframes galleryScrollDown {
from { transform: translateY(0); }
to { transform: translateY(-50%); }
}
@keyframes galleryScrollUp {
from { transform: translateY(-50%); }
to { transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
:global(.gallery-animation__track) {
animation: none;
}
}
</style>