image component to handle loading and error states

This commit is contained in:
Benjamin Palko 2026-01-23 17:26:01 -05:00
parent 7b0880b53b
commit 52d46f3584
6 changed files with 64 additions and 22 deletions

View file

@ -1,17 +1,20 @@
<script lang="ts">
import dayjs from 'dayjs';
import { Link } from '@lucide/svelte';
import Image from './Image.svelte';
interface Props {
id: string;
img: string;
'img-alt': string | null;
img: {
url: string;
alt?: string;
};
title: string;
published: string;
topics: string[];
}
let { id, img, 'img-alt': alt, title, published, topics }: Props = $props();
let { id, img, title, published, topics }: Props = $props();
let blogLink = $derived(`/blog/${id}`);
</script>
@ -23,14 +26,14 @@
<div class="badge badge-soft badge-info">{topic}</div>
{/each}
</div>
<div class="absolute z-10 opacity-0 transition-opacity group-hover:opacity-75">
<div class="absolute z-20 opacity-0 transition-opacity group-hover:opacity-75">
<Link size={32} />
</div>
<a href={blogLink}>
<img
class="z-0 aspect-4/2 w-full object-cover transition-transform group-hover:scale-110 group-hover:blur-xs"
src={img}
{alt}
<a href={blogLink} class="h-full w-full">
<Image
class="z-10 aspect-4/2 w-full object-cover transition-transform group-hover:scale-110 group-hover:blur-xs"
src={img.url}
alt={img.alt}
/>
</a>
</figure>

View file

@ -1,16 +1,46 @@
<script lang="ts">
import { ImageOff } from '@lucide/svelte';
import clsx from 'clsx';
import { onMount } from 'svelte';
import type { ClassValue } from 'svelte/elements';
import { twMerge } from 'tailwind-merge';
interface Props {
src: string;
alt: string;
caption?: string;
alt?: string;
class?: ClassValue;
}
let { src, alt, caption }: Props = $props();
let { src, alt, class: className }: Props = $props();
let loading = $state(true);
let failed = $state(false);
onMount(() => {});
onMount(() => {
const img = new Image();
img.src = src;
loading = true;
img.onload = () => {
loading = false;
};
img.onerror = () => {
loading = false;
failed = true;
};
});
</script>
<figure class="flex flex-col">
<img class="aspect-4/2 w-full object-cover" {src} {alt} />
{#if caption}
<figcaption class="mt-2 text-center text-sm text-gray-500 italic">{caption}</figcaption>
{/if}
</figure>
{#if loading}
<div class={twMerge('skeleton rounded-none', clsx(className))}></div>
{:else if failed}
<div class={twMerge('skeleton text-base-content relative rounded-none', clsx(className))}>
<ImageOff
class="absolute top-[50%] left-[50%] h-1/4 w-1/4 translate-x-[-50%] translate-y-[-50%] opacity-20"
/>
</div>
{:else}
<img class={clsx(className)} {src} {alt} onload={() => (loading = false)} />
{/if}