← Back to site

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

PropTypeDefaultDescription
speednumber50Scroll speed in pixels per second
gapstring"2rem"Gap between items (any CSS length)
pauseOnHoverbooleantruePause animation on hover
height'sm' | 'md' | 'lg' | 'xl''md'Height preset
direction'left' | 'right''left'Scroll direction

Height Presets

PresetValue
sm12rem
md16rem
lg20rem
xl24rem

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()):

  1. Wait for all images to load (with 3-second timeout fallback)
  2. Measure the original content width
  3. Clone items repeatedly until content overflows the container
  4. Add one more complete set as seamless animation buffer
  5. Check prefers-reduced-motion preference
  6. Start requestAnimationFrame loop

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