Build a Reusable Infinite Marquee System in Webflow

Intermediate 47:44 webflowgsapmarqueeanimationattributes

Build an infinite looping marquee in Webflow with GSAP that runs in any direction and is reusable across the whole site via a single attribute — no per-marquee rebuild.

Key takeaways

  • A seamless infinite marquee needs two identical trail groups side by side; animating one group by -100% of its own width and looping lets the second slide in to take its place.
  • Move the trail group by -100% (not a pixel or vw amount) so the loop is independent of how many items there are or how wide they are — a genuinely general solution.
  • Set overflow: hidden on the main container (via a combo class), never on the body; that keeps the visible marquee within your content width instead of stretching edge to edge.
  • Make the interaction reusable with a data-animate attribute and "any element → direct child of → attribute" targeting, so one interaction drives every marquee regardless of class names.
  • Direction is just which axis and which way: X for horizontal, Y for vertical, both for diagonal; reverse by animating from -100% back to 0 instead of 0 to -100%.

Video chapters

  1. 00:00 Intro
  2. 00:48 Building the initial structure
  3. 09:58 Building the core interaction
  4. 17:04 Understanding how the infinite loop should work
  5. 24:50 Optimizing the interaction with custom attributes
  6. 27:26 Building a reversed horizontal marquee
  7. 29:32 Building a diagonal marquee
  8. 30:59 Building a reversed diagonal marquee
  9. 31:50 Adding a CSS hover interaction to the marquee elements
  10. 33:52 Pausing the marquee on hover
  11. 38:21 Building a vertical marquee
  12. 44:52 Building a reversed vertical marquee
  13. 46:40 Outro

The marquee is one of the most loved components by designers and one of the most hated by anyone who has to build it properly. This lesson builds a real one from a blank canvas — not just a marquee that scrolls, but a system: seamless in any direction, reusable across the whole site with a single attribute, and solid enough to trust in production.

More than the result, it’s the reasoning that matters here. Every “why” — why two copies, why -100%, where overflow goes, why an attribute — is what turns a copied snippet into a technique you own.

How it works

Two trail groups make the loop seamless. A single row of items can’t loop cleanly — when it scrolls out there’s nothing behind it, so you get a gap and a jarring reset. The fix is to duplicate the whole content into two identical groups (each a div with the class section_marquee-trail) sitting side by side inside a flex section_marquee. Animate the groups leftward, and just as the first slides fully out, the second is exactly where the first began — reset instantly and it reads as an infinite strip.

Animate by -100%, not a measured width. The naive approach animates the container by a computed amount (item width × count). That’s fragile: it breaks the moment items have different widths or you add one. Because both trail groups have identical width, animating them by -100% of their own width always lands the second group where the first started — no matter how many items or what sizes. That’s the general solution. (Duration ~40s, easing linear so the scroll is perfectly constant. Set the trail groups’ flex sizing to don’t shrink or grow so they take their natural width.)

Overflow on the container, never the body. To hide the off-screen content, set overflow: hidden on the main container through a combo class (is-marquee) rather than the base class — that keeps the visible marquee bounded to your content max-width, which usually looks best, without touching the container everywhere else. Setting overflow hidden on the body is a hard no.

One attribute makes it reusable. Rather than rebuild the interaction per marquee, give each marquee wrapper a data-animate attribute (e.g. horizontal-marquee) and target the action with any element → direct child of → attribute. That animates the two trail groups of any marquee carrying the attribute, independent of its class names — so one interaction powers them all (leave the “first match only” box unchecked, since there are two children).

Direction is just axis + sign. From this base every variant is a small change: horizontal animates move X; vertical animates move Y; diagonal animates both. Reverse any of them by animating from -100% back to 0 instead of 0 to -100%. Pausing on hover is a separate hover interaction (control pause on mouse-enter, resume on mouse-leave), and you can layer a CSS hover on the items themselves for extra life.

How to use it

  1. Structure. Section → main container (with a max-width) → section_marquee (display flex). Inside, wrap all your items in a section_marquee-trail div (flex), then duplicate that trail so there are two identical groups. Set the trail groups’ flex sizing to don’t shrink or grow; add a small right margin to items for spacing.

  2. Clip it. Add an is-marquee combo class to the main container with overflow: hidden and zero horizontal padding.

  3. Core interaction. Page-load GSAP interaction. Give the section_marquee wrapper data-animate="horizontal-marquee". Action: target any element → direct child of → data-animate=horizontal-marquee, animate move X to -100%, duration ~40s, linear, repeat: infinite.

  4. Variants. Duplicate the section, change the attribute value (e.g. reversed-horizontal-marquee) and the action’s target to match. Reverse = animate move X from -100% to 0. Vertical = animate move Y. Diagonal = animate X and Y together. Reversed diagonal = flip both.

  5. Pause on hover. Add a hover interaction on the marquee: mouse-enter → control pause, mouse-leave → control resume.

Resources

Frequently asked questions

How do I make a marquee loop seamlessly with no gap or jump?
Duplicate the marquee content into two identical trail groups placed side by side. Animate the group by -100% of its own width and loop it — as the first group scrolls out, the second occupies exactly the position the first started from, so the reset is invisible and the loop looks endless.
Why animate by -100% instead of a fixed pixel or vw value?
Because -100% is relative to the trail group's own width, the loop works no matter how many items it holds or how wide each one is. A fixed pixel/vw amount only works for one specific item count and size, which isn't a reusable solution.
Where should I put overflow: hidden for a marquee?
On the main container, via a combo class (e.g. is-marquee) so you don't affect the base container everywhere. Never set overflow: hidden on the body. Putting it on the container keeps the visible marquee within your content max-width rather than stretching to the viewport edges.
How can I reuse one marquee interaction across marquees with different classes?
Give each marquee wrapper a shared custom attribute like data-animate="horizontal-marquee", then set the action target to "any element → direct child of → attribute" pointing at that attribute. One interaction now animates the trail groups of every matching marquee, independent of class names.
How do I change the direction of a marquee?
Pick the axis and the sign. Horizontal animates move X, vertical animates move Y, diagonal animates both together. To reverse a direction, flip the animation so it goes from -100% back to 0 instead of from 0 to -100%.

← Back to the course