ImageWithCaption image
Editoriaal beeld met gestyled onderschrift en optioneel fotocredits. Drie breedtemodi: full, contained, narrow.
src/components/image/ImageWithCaption.astro
---
interface Props {
src: string;
alt?: string;
caption?: string;
credit?: string;
width?: 'full' | 'contained' | 'narrow';
aspectRatio?: string;
rounded?: boolean;
}
const {
src,
alt = '',
caption,
credit,
width = 'contained',
aspectRatio,
rounded = true,
} = Astro.props;
---
<figure class={`iwc__figure iwc__figure--${width}`}>
<div
class={`iwc__img-wrap${rounded ? ' iwc__img-wrap--rounded' : ''}`}
style={aspectRatio ? `aspect-ratio: ${aspectRatio};` : ''}
>
<img
src={src}
alt={alt}
class="iwc__img"
loading="lazy"
decoding="async"
/>
</div>
{(caption || credit) && (
<figcaption class="iwc__meta">
{caption && <p class="iwc__caption">{caption}</p>}
{credit && <span class="iwc__credit">{credit}</span>}
</figcaption>
)}
</figure>
<script>
const figures = document.querySelectorAll<HTMLElement>('.iwc__figure');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('iwc__figure--visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
figures.forEach((fig) => observer.observe(fig));
</script>
<style>
:root {
--color-primary: #0a0a0a;
--color-accent: #6366f1;
--color-bg: #fff;
--color-muted: #6b7280;
--radius: 0.5rem;
}
.iwc__figure {
margin: 2.5rem 0;
padding: 0;
opacity: 0;
transform: translateY(16px);
transition: opacity 0.55s ease, transform 0.55s ease;
}
.iwc__figure--visible {
opacity: 1;
transform: translateY(0);
}
/* Width variants */
.iwc__figure--full {
width: 100vw;
margin-left: calc(50% - 50vw);
margin-right: calc(50% - 50vw);
}
.iwc__figure--contained {
max-width: 960px;
margin-left: auto;
margin-right: auto;
padding: 0 1.5rem;
}
.iwc__figure--narrow {
max-width: 640px;
margin-left: auto;
margin-right: auto;
padding: 0 1.5rem;
}
.iwc__img-wrap {
overflow: hidden;
width: 100%;
}
.iwc__img-wrap--rounded {
border-radius: var(--radius);
}
/* If aspect ratio is set via inline style, enforce object-fit */
.iwc__img-wrap[style*='aspect-ratio'] .iwc__img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.iwc__img-wrap[style*='aspect-ratio'] {
position: relative;
}
.iwc__img {
display: block;
width: 100%;
height: auto;
object-fit: cover;
}
.iwc__meta {
margin: 0;
padding: 0.625rem 0 0;
border-top: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.iwc__caption {
margin: 0;
font-size: 0.875rem;
font-style: italic;
color: var(--color-muted);
line-height: 1.5;
}
.iwc__credit {
font-size: 0.75rem;
color: #9ca3af;
letter-spacing: 0.02em;
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
src * | string | — | Afbeelding URL |
caption | string | — | Onderschrift tekst |
credit | string | — | Fotocredits (kleiner dan caption) |
width | 'full' | 'contained' | 'narrow' | 'contained' | Breedte variant |
aspectRatio | string | — | Beeldverhouding, bijv. '16/9' |
* = verplicht