Zoeken...  ⌘K GitHub

ImageWithCaption image

Editoriaal beeld met gestyled onderschrift en optioneel fotocredits. Drie breedtemodi: full, contained, narrow.

/image-with-caption
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