BannerImage
apps/www/src/sections/BannerImage.astro · 80 lines · group Dividers
BannerImage — a full-width band of one photo, or of several photos cross-fading into each other. Use for: a visual pause between sections — a workshop, a landscape, a product line — with no text on it. Not for: a first screen with a title (<HeroCover>); a photo with a caption (<Gallery>). Children: none. Pictures: `imageName` is one file name in images/ or an array of them; use WIDE photos (the default band is about 4.4:1, `aspect` changes it as a Tailwind aspect class such as "aspect-[3/1]"). Several photos rotate every `interval` seconds, and stay still for visitors who prefer reduced motion.
In the fleet
Used on 1 site built with this engine.
Samples 1



MDX of this sample
<BannerImage imageName={["photo-wide-01.jpg", "photo-wide-02.jpg", "photo-wide-03.jpg"]} />Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| imageName | string | string[] | yes | one file name in images/, or several to cross-fade between | |
| alt | string | '' | alt text, only when the photo carries meaning; a decorative band stays silent | |
| interval | number | 6 | seconds each photo stays up; ignored for a single photo | |
| aspect | string | 'aspect-[3840/880]' | the band's proportions, as a Tailwind aspect class, e.g. "aspect-[3/1]" |
Source apps/www/src/sections/BannerImage.astro @ gitt.one
---
/**
* BannerImage — a full-width band of one photo, or of several photos
* cross-fading into each other.
*
* Use for: a visual pause between sections — a workshop, a landscape, a
* product line — with no text on it.
* Not for: a first screen with a title (<HeroCover>); a photo with a caption
* (<Gallery>).
* Children: none.
* Pictures: `imageName` is one file name in images/ or an array of them; use
* WIDE photos (the default band is about 4.4:1, `aspect` changes it as a
* Tailwind aspect class such as "aspect-[3/1]"). Several photos rotate every
* `interval` seconds, and stay still for visitors who prefer reduced motion.
*/
import { Image } from 'astro:assets';
import { resolveImage } from '../lib/images';
interface Props {
/** one file name in images/, or several to cross-fade between */
imageName: string | string[];
/** alt text, only when the photo carries meaning; a decorative band stays silent */
alt?: string;
/** seconds each photo stays up; ignored for a single photo */
interval?: number;
/** the band's proportions, as a Tailwind aspect class, e.g. "aspect-[3/1]" */
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@7b686388. Samples are demo content: the components are the real ones, the copy and the pictures are made up.