Zoeken...  ⌘K GitHub

ImageGallery image

ImageGallery component.

/image-gallery
src/components/image/ImageGallery.astro
---
/**
 * ImageGallery — heading + responsive grid van afbeeldingen met hover-caption.
 *
 * Props:
 * - eyebrow?: string
 * - headline?: string
 * - columns?: number
 * - gap?: string
 * - items?: { src: string; alt?: string; caption?: string }[]
 */
interface Item {
  src: string;
  alt?: string;
  caption?: string;
}
interface Props {
  eyebrow?: string;
  headline?: string;
  columns?: number;
  gap?: string;
  items?: Item[];
}
const {
  eyebrow,
  headline,
  columns = 3,
  gap = '1rem',
  items = [
    { src: '/img/ext/photo-1460925895917-afdab827c52f-w800-646ccd.jpg', alt: 'Dashboard analyse', caption: 'Performance dashboard' },
    { src: '/img/ext/photo-1551434678-e076c223a692-w800-09e674.jpg', alt: 'Team overleg', caption: 'Strategiesessie' },
    { src: '/img/ext/photo-1542744173-8e7e53415bb0-w800-cea885.jpg', alt: 'Presentatie', caption: 'Klantpresentatie' },
    { src: '/img/ext/photo-1504868584819-f8e8b4b6d7e3-w800-777316.jpg', alt: 'Data visualisatie', caption: 'Campagne inzichten' },
    { src: '/img/ext/photo-1522071820081-009f0129c71c-w800-fff847.jpg', alt: 'Teamfoto', caption: 'Het team' },
    { src: '/img/ext/photo-1611532736597-de2d4265fba3-w800-4b9f1e.jpg', alt: 'Werkplek', caption: 'De studio' },
  ],
} = Astro.props;
---

<section class="bl-section ig">
  <div class="bl-inner ig__inner">
    {(eyebrow || headline) && (
      <div class="ig__header">
        {eyebrow && <p class="ig__eyebrow">{eyebrow}</p>}
        {headline && <h2 class="ig__headline">{headline}</h2>}
      </div>
    )}
    <div class="ig__grid" data-columns={String(columns)} style={`--ig-columns: ${columns}; --ig-gap: ${gap};`}>
      {items.map((it, i) => (
        <div class="ig__item ig__item--rounded" style={`--ig-delay: ${i * 80}ms;`}>
          <div class="ig__img-wrap">
            <img data-allow-img src={it.src} alt={it.alt || ''} class="ig__img" loading="lazy" decoding="async" />
            {it.caption && (
              <div class="ig__caption">
                <span class="ig__caption-text">{it.caption}</span>
              </div>
            )}
          </div>
        </div>
      ))}
    </div>
  </div>
</section>

<style>
.ig{background:var(--color-bg)}
.ig__header{margin-bottom:2.5rem}
.ig__eyebrow{font-size:.75rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--color-accent);margin:0 0 .5rem}
.ig__headline{font-size:clamp(1.75rem,4vw,2.75rem);font-weight:700;color:var(--color-primary);margin:0;line-height:1.15}
.ig__grid{display:grid;grid-template-columns:repeat(var(--ig-columns, 3),1fr);gap:var(--ig-gap, 1rem)}
.ig__item{opacity:0;transform:translateY(24px);transition:opacity .5s ease var(--ig-delay, 0ms),transform .5s ease var(--ig-delay, 0ms)}
.ig__item--visible{opacity:1;transform:translateY(0)}
.ig__img-wrap{position:relative;aspect-ratio:4 / 3;overflow:hidden}
.ig__item--rounded .ig__img-wrap{border-radius:var(--radius)}
.ig__img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .35s ease,filter .35s ease}
.ig__img-wrap:hover .ig__img{transform:scale(1.03);filter:brightness(1.05)}
.ig__caption{position:absolute;bottom:0;left:0;right:0;padding:1.5rem 1rem .75rem;background:linear-gradient(to top,rgba(0,0,0,.65) 0%,transparent 100%);opacity:0;transition:opacity .3s ease}
.ig__img-wrap:hover .ig__caption{opacity:1}
.ig__caption-text{color:#fff;font-size:.8125rem;line-height:1.4}
@media (max-width:900px){.ig__grid{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.ig__grid{grid-template-columns:1fr}}
</style>

<script>
  const items = document.querySelectorAll('.ig__item');
  if (items.length) {
    if ('IntersectionObserver' in window) {
      const io = new IntersectionObserver((entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add('ig__item--visible');
            io.unobserve(e.target);
          }
        });
      }, { threshold: 0.15 });
      items.forEach((el) => io.observe(el));
    } else {
      items.forEach((el) => el.classList.add('ig__item--visible'));
    }
  }
</script>