BannerImage

apps/www/src/sections/BannerImage.astro · 66 lines · group Dividers

In the fleet

Used on 1 site built with this engine.

Samples 1

sample
MDX of this sample
<BannerImage imageName={["photo-wide-01.jpg", "photo-wide-02.jpg", "photo-wide-03.jpg"]} />

Props

NameTypeRequiredDefaultDescription
imageNamestring | string[]yesone image, or several to cross-fade between
altstring''described only when it carries meaning; a decorative band stays silent
intervalnumber6seconds each image stays up; ignored for a single image
aspectstring'aspect-[3840/880]'the band's proportions, as a Tailwind aspect class

Source apps/www/src/sections/BannerImage.astro @ gitt.one

---
import { Image } from 'astro:assets';
import { resolveImage } from '../lib/images';

interface Props {
  /** one image, or several to cross-fade between */
  imageName: string | string[];
  /** described only when it carries meaning; a decorative band stays silent */
  alt?: string;
  /** seconds each image stays up; ignored for a single image */
  interval?: number;
  /** the band's proportions, as a Tailwind aspect class */
  aspect?: string;
}

const { imageName, alt = '', interval = 6, aspect = 'aspect-[3840/880]' } = Astro.props;
const names = Array.isArray(imageName) ? imageName : [imageName];
const images = await Promise.all(names.map((name) => resolveImage(name)));
---

<div
  class:list={[
    // the band follows the photo's own proportions, so nothing is cut off the
    // top or bottom; the bounds only bite on very narrow or very wide screens
    'relative max-h-[36rem] min-h-64 w-full overflow-hidden',
    aspect,
  ]}
  data-banner={images.length > 1 ? String(interval) : undefined}
>
  {
    images.map((image, i) => (
      <Image
        src={image}
        alt={i === 0 ? alt : ''}
        widths={[800, 1440, 1920, 2560]}
        loading={i === 0 ? 'eager' : 'lazy'}
        fetchpriority={i === 0 ? 'high' : 'auto'}
        class:list={[
          'absolute inset-0 h-full w-full object-cover object-center transition-opacity duration-1000',
          i === 0 ? 'opacity-100' : 'opacity-0',
        ]}
      />
    ))
  }
</div>

<script>
  // cross-fade the banner photos, unless the visitor asked for less motion.
  // Waits for load: nothing should be moving while the page is still arriving.
  window.addEventListener('load', () => {
    document.querySelectorAll<HTMLElement>('[data-banner]').forEach((banner) => {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      const slides = Array.from(banner.querySelectorAll('img'));
      let current = 0;
      setInterval(
        () => {
          slides[current].classList.replace('opacity-100', 'opacity-0');
          current = (current + 1) % slides.length;
          slides[current].classList.replace('opacity-0', 'opacity-100');
        },
        Number(banner.dataset.banner) * 1000,
      );
    });
  });
</script>

Source links point to engine-astro@38c294d9. Samples are demo content: the components are the real ones, the copy and the pictures are made up.