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):
| Token | Effect |
|---|---|
| (empty) | Loop leftward at the configured aa-duration. |
right | Loop rightward (timeScale = -1 on the same tween). |
paused | Start paused. Resume by toggling tokens at runtime, or driving via JS. |
hover-pause | Pause on mouseenter, resume on mouseleave. Skipped on touch devices and when draggable is set. |
switch | Reverse direction while the page is scrolling up (driven by body[aa-scroll-direction]). |
draggable | Pointer + touch drag modulates the loop speed; release settles to a steady cruise in the flick direction. |
none | Don’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>Structural attributes
Section titled “Structural attributes”Three authored wrappers, each with one role. Required nesting:
[aa-marquee] > [aa-marquee-scroller] > [aa-marquee-track] > [aa-marquee-list].
| Attribute | Role |
|---|---|
aa-marquee | The 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-scroller | The scroll-driven sweep layer. With aa-scrub, this element drifts horizontally with scroll position. Without aa-scrub, it sits idle — no transform. |
aa-marquee-track | The 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-list | The 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.
Spacing strategy
Section titled “Spacing strategy”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.
Config attributes
Section titled “Config attributes”| Attribute | Default | Notes |
|---|---|---|
aa-duration | 20 | Seconds 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-distance | 10 | (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. |
Required GSAP plugins
Section titled “Required GSAP plugins”gsap, ScrollTrigger. Draggable + InertiaPlugin are additionally required when draggable is used (the same pair the slider needs, so most projects already load them).
Composition rules
Section titled “Composition rules”aa-scrublayers on top of the loop — the track keeps cycling, plus a scroll-driven horizontal drift on the scroller layer. Combine freely withright(flips drift direction) andpaused(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.draggabledisableshover-pauseandswitchfor the same element — all three would fight for control of the loop’s progress / direction. Pick one interaction model per loop.pausedwins 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.
Live demo — basic loop
Section titled “Live demo — basic loop”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.
Live demo — direction switch on scroll
Section titled “Live demo — direction switch on scroll”aa-marquee="switch" flips direction whenever the page is scrolling up. Scroll the page back and forth to see it react.
Live demo — scroll-driven sweep
Section titled “Live demo — scroll-driven sweep”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.
Live demo — draggable
Section titled “Live demo — draggable”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.