142 lines
3.2 KiB
Svelte
142 lines
3.2 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(index: number) {
|
|
const img = images[index];
|
|
img.classList.remove("active");
|
|
}
|
|
|
|
const heightCache = [];
|
|
function show(index: number) {
|
|
const img = images[index];
|
|
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") ?? "";
|
|
if (heightCache[index]) {
|
|
height = heightCache[index];
|
|
}
|
|
setTimeout(() => {
|
|
height = heightCache[index] ?? _img.getBoundingClientRect().height;
|
|
heightCache[index] = height;
|
|
}, 100);
|
|
}
|
|
|
|
let index = 0;
|
|
function setIndex(i: number) {
|
|
if (i < 0) i = images.length - 1;
|
|
if (i >= images.length) i = 0;
|
|
hide(index);
|
|
index = i;
|
|
show(index);
|
|
}
|
|
|
|
$: if (slot && !images?.length) {
|
|
images = Array.from(slot.querySelectorAll("picture"));
|
|
if (images?.length) {
|
|
images.forEach((_, i) => hide(i));
|
|
show(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={true}
|
|
class:not-loaded={!loaded}
|
|
class:loaded
|
|
style={`--height:${height}px`}>
|
|
<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>
|
|
<button
|
|
class="flex-1 i-tabler-arrow-right"
|
|
aria-label="next image"
|
|
on:click={() => setIndex(index + 1)}></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="images border-block 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>
|