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