add image carousel location bar

This commit is contained in:
2025-11-23 23:05:02 +01:00
parent da387c076f
commit 44e42b0eef

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import cat1 from '@assets/img/cat1.jpg'; import cat1 from '@assets/img/cat1.jpg';
import cat2 from '@assets/img/cat2.jpg'; import cat2 from '@assets/img/cat2.jpg';
import { onMount } from 'svelte'; import { untrack } from 'svelte';
// html bindings // html bindings
let imageContainer: HTMLDivElement; let imageContainer: HTMLDivElement;
@@ -14,46 +14,62 @@
// states // states
const { images = [{ path: cat1.src }, { path: cat2.src }] }: Props = $props(); const { images = [{ path: cat1.src }, { path: cat2.src }] }: Props = $props();
let currentImage = $state<HTMLDivElement | null>(null); let currentImageIdx = $state<number | null>(images.length > 0 ? 0 : null);
let initialCurrentImageIdxEffect = $state(false);
// lifecycle // lifecycle
onMount(() => { $effect(() => {
currentImage = imageContainer.children.item(0) as HTMLDivElement | null; // this check must be first. otherwise this effect will not be called besides on mount
if (currentImageIdx == null) return;
else if (untrack(() => (initialCurrentImageIdxEffect ? false : (initialCurrentImageIdxEffect = true)))) return;
imageContainer.children.item(currentImageIdx)!.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}); });
// callback // callback
function scrollToIdx(idx: number) {
currentImageIdx = idx;
}
function scrollPrev() { function scrollPrev() {
if (!currentImage!.previousElementSibling) return; if (currentImageIdx == null || currentImageIdx == 0) return;
currentImage!.previousElementSibling.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); currentImageIdx -= 1;
currentImage = currentImage!.previousElementSibling as HTMLDivElement;
} }
function scrollNext() { function scrollNext() {
if (!currentImage!.nextElementSibling) return; if (currentImageIdx == null || currentImageIdx == images.length - 1) return;
currentImage!.nextElementSibling.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); currentImageIdx += 1;
currentImage = currentImage!.nextElementSibling as HTMLDivElement;
} }
</script> </script>
<div class="relative"> <div class="relative">
<div class="carousel w-full aspect-video rounded items-center" bind:this={imageContainer}> <div class="carousel w-full aspect-video rounded items-center overflow-hidden" bind:this={imageContainer}>
{#each images as image (image.path)} {#each images as image (image.path)}
<div class="carousel-item w-full"> <div class="carousel-item w-full">
<img src={image.path} class="w-full object-cover" alt={image.text ?? image.path} /> <img src={image.path} class="w-full object-cover" alt={image.text ?? image.path} />
</div> </div>
{/each} {/each}
</div> </div>
{#if currentImage} {#if currentImageIdx != null}
<div class="absolute bottom-6 w-full flex justify-center gap-2.5">
{#each images as image, i (image.path)}
<button
aria-label={`scroll to image ${i}`}
class="w-2.5 h-2.5 bg-white opacity-50 rounded-full cursor-pointer"
class:opacity-100={currentImageIdx === i}
onclick={() => scrollToIdx(i)}
></button>
{/each}
</div>
<div class="absolute top-0 left-2 h-full flex items-center"> <div class="absolute top-0 left-2 h-full flex items-center">
<button <button
class="backdrop-invert-75 w-10 h-10 rounded-full flex justify-center items-center" class="backdrop-invert-75 w-10 h-10 rounded-full flex justify-center items-center cursor-pointer"
aria-label="previous" aria-label="previous"
onclick={scrollPrev}><span class="iconify iconify-[heroicons--chevron-left] w-2/5 h-2/5"></span></button onclick={scrollPrev}><span class="iconify iconify-[heroicons--chevron-left] w-2/5 h-2/5"></span></button
> >
</div> </div>
<div class="absolute top-0 right-2 h-full flex items-center"> <div class="absolute top-0 right-2 h-full flex items-center">
<button <button
class="backdrop-invert-75 w-10 h-10 rounded-full flex justify-center items-center" class="backdrop-invert-75 w-10 h-10 rounded-full flex justify-center items-center cursor-pointer"
aria-label="next" aria-label="next"
onclick={scrollNext}><span class="iconify iconify-[heroicons--chevron-right] w-2/5 h-2/5"></span></button onclick={scrollNext}><span class="iconify iconify-[heroicons--chevron-right] w-2/5 h-2/5"></span></button
> >