Marquee
The Marquee component creates a seamless infinite horizontal scroll effect. It works with any content including Img components, automatically cloning children to fill the viewport and create a continuous loop.
Import
import Marquee from '@mdx/Marquee.astro';
Props
| Prop | Type | Default | Description |
|---|---|---|---|
speed | number | 50 | Scroll speed in pixels per second |
gap | string | "2rem" | Gap between items (any CSS length) |
pauseOnHover | boolean | true | Pause animation on hover |
height | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Height preset |
direction | 'left' | 'right' | 'left' | Scroll direction |
Height Presets
| Preset | Value |
|---|---|
sm | 12rem |
md | 16rem |
lg | 20rem |
xl | 24rem |
Children are passed as a slot. Each direct child becomes a marquee item.
Usage
<Marquee speed={40} height="lg" gap="1.5rem">
<Img src={photo1} alt="Photo 1" enableClickToEnlarge={false} />
<Img src={photo2} alt="Photo 2" enableClickToEnlarge={false} />
<Img src={photo3} alt="Photo 3" enableClickToEnlarge={false} />
</Marquee>
Implementation Notes
Initialization flow (Alpine.js init()):
- Wait for all images to load (with 3-second timeout fallback)
- Measure the original content width
- Clone items repeatedly until content overflows the container
- Add one more complete set as seamless animation buffer
- Check
prefers-reduced-motionpreference - Start
requestAnimationFrameloop
Animation: Uses a requestAnimationFrame loop with delta-time calculation. Each frame advances position by speed * deltaTime pixels, then applies transform: translateX(-position) on the track element. Position wraps with modulo at totalWidth (one original item set width) for seamless looping. Delta-time is capped at 100ms to prevent jumps after tab switches.
Touch support: The container sets touch-action: pan-y pinch-zoom so the browser handles vertical scrolling and pinch-zoom while JS handles horizontal dragging. On touchstart, the current position is recorded. During touchmove, the position updates based on finger delta (wrapping with modulo). On touchend, auto-scrolling resumes smoothly. Multi-touch aborts the drag to allow pinch-zoom.
Reduced motion: Auto-scrolling is disabled when prefers-reduced-motion: reduce is active, but touch dragging still works.
Image sizing: The Marquee automatically computes a sizes attribute for every <img> inside it at build time. It uses the selected height preset (minus 16px of vertical padding) and each image’s intrinsic aspect ratio to calculate the exact display width: calc((<height> - 16px) * <aspectRatio>). This means consumers never need to pass a manual sizes prop to Img components inside a Marquee — it is computed and injected automatically.
Img compatibility: Special CSS rules ensure Img wrapper divs (.img-better) fill height correctly within the marquee.
Source: src/components/mdx/Marquee.astro