Skip to content

marquee

aa-marquee turns a list of items into a seamlessly looping horizontal marquee. The library measures the list, clones it as many times as needed to fill the viewport, then drives an infinite GSAP tween with a wrap modifier — no jump, no reset frame.

aa-marquee modes (space-separated tokens, order-independent):

TokenEffect
(empty)Loop leftward at the configured aa-duration.
rightLoop rightward (timeScale = -1 on the same tween).
pausedStart paused. Resume by toggling tokens at runtime, or driving via JS.
hover-pausePause on mouseenter, resume on mouseleave. Skipped on touch devices and when draggable is set.
switchReverse direction while the page is scrolling up (driven by body[aa-scroll-direction]).
draggablePointer + touch drag modulates the loop speed; release settles to a steady cruise in the flick direction.
noneDon’t init at this breakpoint. Combine with | (splits at md) or -sm / -md / -lg / -xl.

Add the aa-scrub attribute (separately) to layer a scroll-driven sweep on top of the loop — the row drifts left↔right as you scroll, while still cycling. See scrub below.

<div aa-marquee="right hover-pause" aa-duration="30">
<div aa-marquee-scroller>
<div aa-marquee-track>
<div aa-marquee-list></div>
</div>
</div>
</div>
<!-- Static row on desktop, scrolling on mobile -->
<div aa-marquee="none|" aa-duration="none|18"></div>

Three authored wrappers, each with one role. Required nesting: [aa-marquee] > [aa-marquee-scroller] > [aa-marquee-track] > [aa-marquee-list].

AttributeRole
aa-marqueeThe viewport. The lib’s CSS applies overflow: hidden automatically; the attribute is also the ScrollTrigger target that gates the loop to on-screen visibility.
aa-marquee-scrollerThe scroll-driven sweep layer. With aa-scrub, this element drifts horizontally with scroll position. Without aa-scrub, it sits idle — no transform.
aa-marquee-trackThe infinite-loop layer. The lib applies the looping translate here, and clones of [aa-marquee-list] (marked aa-marquee-clone) land here at runtime.
aa-marquee-listThe authored repeating unit. Items inside carry the spacing via margin. The lib measures scrollWidth here to compute the cycle distance.

The lib also sets aa-marquee-direction="left" \| "right" on the root while running — useful for CSS state hooks (e.g. flipping inner item rotation per direction).

The two transforms (scroller’s scroll-driven sweep + track’s infinite loop) sit on independent DOM layers, so the browser composes them additively — neither tween clobbers the other. With or without scrub, the markup shape stays the same; the scroller just becomes a no-op when scrub is absent.

Structural CSS ships in alrdy-animate.css: every wrapper gets display: flex; align-items: center, the inner three get flex: none, the viewport gets overflow: hidden, and the animated layers get will-change: transform. Authors only need to set width/height/spacing cosmetics — no flex-layout boilerplate required.

Use margin on each item to space items out. margin-right alone, or margin-left + margin-right — both work and produce uniform spacing across the wrap point. The lib measures [aa-marquee-list].scrollWidth (which folds margins into the cycle distance), so the seam between cloned lists matches the gap inside a list automatically. No JS configuration needed.

.marquee-item {
margin-right: 1rem; /* OK */
/* or */
margin: 0 0.5rem; /* also OK — left + right give equivalent spacing */
}

CSS gap / column-gap on the track is intentionally not supported — gap sits between siblings only and never contributes to the list’s own width, so cloned lists would end up tighter at the seam than items are within a list. Stick to margin.

AttributeDefaultNotes
aa-duration20Seconds for the track to translate one full list-width. Lower = faster.
aa-scrub(off)Presence enables the scroll-driven sweep. Value sets the ScrollTrigger scrub: true for an instant lock, or seconds for smoothing lag.
aa-distance10(aa-scrub only) Sweep magnitude as a percentage of viewport width per side. 10 = ±10vw (20vw total sweep). Independent of cycle distance, so the effect feels consistent at every screen size.

gsap, ScrollTrigger. Draggable + InertiaPlugin are additionally required when draggable is used (the same pair the slider needs, so most projects already load them).

  • aa-scrub layers on top of the loop — the track keeps cycling, plus a scroll-driven horizontal drift on the scroller layer. Combine freely with right (flips drift direction) and paused (pure scroll-driven, no autoplay). The scroller and track sit on separate transform layers in markup so the two motions compose without one overriding the other.
  • draggable disables hover-pause and switch for the same element — all three would fight for control of the loop’s progress / direction. Pick one interaction model per loop.
  • paused wins for the loop: a paused marquee never auto-cycles, regardless of viewport entry, hover, or scroll direction. Scrub still works on top of it.
  • The whole loop pauses automatically when the marquee scrolls out of the viewport (top-bottom / bottom-top trigger), matching the slider/tabs pattern.
Continuous
Loop
No
Reset
Frame
Ever

Live demo — right-direction with hover-pause

Section titled “Live demo — right-direction with hover-pause”

aa-marquee="right hover-pause" reverses the loop and stops it under the cursor. Mouseleave resumes.

Hover
Me
To
Pause
Then
Release

aa-marquee="switch" flips direction whenever the page is scrolling up. Scroll the page back and forth to see it react.

Scroll
Down
Then
Scroll
Up

Add the aa-scrub attribute to layer a scroll-driven left↔right drift on top of the continuous loop. As the marquee enters the bottom of the viewport (top bottom) and exits the top (bottom top), the row drifts by aa-distance × 2 vw total — aa-distance="10" is ±10vw per side (20vw total). Pass a number to aa-scrub for smoothing lag. The loop keeps cycling underneath, so the row stays alive even when you stop scrolling. Add right to flip the drift direction; add paused to drop the loop and get pure scroll-driven motion.

Scroll up and down past this demo to see both motions compose.

Sweep
With
Scroll
Position
Not
Time

aa-marquee="draggable" adds pointer + touch drag. Flick to push the loop faster in either direction; release and the loop settles back to a steady cruise in the direction of your last flick.

Drag
Me
Or
Flick
Hard