Files
max-richter.dev/src/components/ImageSlider.svelte
2025-10-22 22:32:34 +02:00

138 lines
3.1 KiB
Svelte

<script lang="ts">
let slot: HTMLDivElement;
let images: (HTMLPictureElement | HTMLImageElement)[];
export let title: string;
let altText: string;
let height: number;
let loaded = false;
function hide(img: HTMLPictureElement) {
img.classList.remove("active");
}
function show(img: HTMLPictureElement) {
img.classList.add("active");
const _img = img.querySelector("img") || img;
if (!_img) return;
_img.addEventListener("load", () => {
img.classList.remove("thumb-loading");
_img.style.opacity = "1";
});
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
height = _img.getBoundingClientRect().height;
setTimeout(() => {
height = _img.getBoundingClientRect().height;
}, 100);
}
let index = 0;
function setIndex(i: number) {
if (i < 0) i = images.length - 1;
if (i >= images.length) i = 0;
hide(images[index]);
index = i;
show(images[index]);
}
$: if (slot && !images?.length) {
images = Array.from(slot.querySelectorAll("picture"));
if (images?.length) {
images.forEach(hide);
show(images[index]);
images[index].onload = () => {
loaded = true;
height = images[index].getBoundingClientRect().height;
};
}
setTimeout(() => {
loaded = true;
}, 100);
}
</script>
<div
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
class:title
class:not-loaded={!loaded}
class:loaded
style={`--height:${height}px`}>
{#if title}
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
<h3>{title}</h3>
<div
class="overflow-hidden rounded-md bg-light gap-2 flex p-2 border border-light">
<button
class="flex-1 i-tabler-arrow-left"
aria-label="previous image"
on:click={() => setIndex(index - 1)} />
<button
class="flex-1 i-tabler-arrow-right"
aria-label="next image"
on:click={() => setIndex(index + 1)} />
</div>
</div>
{/if}
<div class="images border-t-1 border-b-1 border-neutral" bind:this={slot}>
<slot />
</div>
<div class="px-4 flex items-center place-content-between bg">
<p>
{#if images?.length}
{index + 1}/{images?.length}
{/if}
</p>
{#if altText}
<p class="flex-1 text-center">{altText}</p>
{/if}
</div>
</div>
<style>
.wrapper {
grid-template-rows: 1fr 50px;
transition: height 0.3s;
}
.wrapper :global(.image-wrapper) {
margin: 0 !important;
}
.wrapper.title {
grid-template-rows: 50px 1fr 50px;
}
.loaded {
height: calc(var(--height) + 50px);
}
.not-loaded .images :global(picture):first-child {
display: block !important;
}
.wrapper.title.loaded {
height: calc(var(--height) + 100px);
}
.images :global(picture) {
border-radius: 0px;
display: none;
}
.images :global(.thumb-loading)::before {
opacity: 0;
}
.images :global(.active) {
position: relative;
display: block !important;
opacity: 1;
transition:
opacity 0.3s ease,
display 0.3s 0.3s;
}
</style>