Seamless loop
A seamless loop is a GSAP animation set to `repeat: -1` whose end state matches its start state exactly, so it plays forever with no visible jump at the loop point. It's the core technique behind marquees, logo ribbons, tickers, and any motion that needs to run continuously without the eye ever catching the seam.
Updated June 30, 2026
How a seamless loop works
repeat: -1 tells a tween or timeline to play again the moment it finishes, indefinitely. That alone gives you an infinite loop, but not a seamless one. The seam appears when frame 0 and the final frame don't line up: the playhead snaps back to the start and you see a jump.
The fix is to make the start and end states identical. For a horizontal marquee, you duplicate the content and animate the track by exactly the width of one copy. When the first copy has scrolled fully off, the duplicate is sitting in precisely the position the first copy started in, so the reset is invisible.
ease: "none" (linear) is non-negotiable for a continuous loop. Any easing curve speeds up and slows down across each pass, so the motion visibly pulses at every repeat boundary. Constant velocity is what reads as 'one endless ribbon' instead of 'the same animation playing over and over'.
yoyo is not a loop
yoyo: true reverses direction on every repeat, so the content scrolls left, then right, then left. That's a ping-pong, not a marquee. Use yoyo for back-and-forth pulses (a breathing scale, a hover shimmer), never for content that should travel one direction forever.The modifiers + wrap approach
Duplicating content covers most marquees. When you need many items looping independently, or drag control, or items that wrap individually rather than as one block, use a modifiers function with gsap.utils.wrap. The modifier rewrites each frame's value so anything that scrolls past the end is mapped back to the start, keeping every element inside a fixed range.
This is the engine inside GSAP's well-known horizontalLoop helper: a repeat: -1 timeline of per-item tweens, with modifiers: { time: gsap.utils.wrap(0, duration) } so the playhead itself wraps. It's heavier than the duplicate-and-translate trick, so reach for it only when you actually need per-item control or scrubbing.
Use a seamless loop for
- Horizontal marquees and partner-logo ribbons
- Text tickers and announcement bars
- Vertical testimonial feeds that scroll on their own
- Continuous shimmer or gradient sweeps across text
- Any decorative motion that should run as long as the section is on screen
Use something else when
- The motion is a single back-and-forth pulse, use
repeat: -1withyoyo: trueinstead of matched start/end states - The loop should react to scroll speed rather than run at a fixed rate, that's scroll-velocity (see the Velocity Type component), not a timeline loop
- You only need a finite number of passes, set
repeat: 3and skip the seam-matching entirely - It's a purely cosmetic CSS-only marquee with no drag or pause logic, a CSS
@keyframestranslate loop is lighter than loading GSAP for that one job
Used in these Annnimate components
- The Infinite Marquee runs a
repeat: -1timeline withease: "none", clones the content to fill the track, and addsgsap.utils.wrapmodifiers so dragging and momentum stay inside the loop - The Text Shimmer Wave uses
repeat: -1with a smallrepeatDelayto sweep a highlight across the characters on a constant cadence
Keep the loop composited
xPercent / x (transform-only, GPU-composited), never left or margin, or a constantly-running marquee becomes the jank on the page.See it running in production
Common questions
- How do I make an infinite marquee in GSAP?
- Duplicate your content so the track holds two identical copies side by side, then tween the track to
xPercent: -50withdurationset to taste,ease: "none", andrepeat: -1. Because the second copy is sitting exactly where the first started, the reset at the end of each pass is invisible. That's the whole trick: matched start/end states plus a linear ease. - Why does my marquee jump or stutter at the loop point?
- Two usual causes. Either your end state doesn't match the start state (you animated by the wrong distance, so frame 0 and the last frame differ and the snap-back is visible), or you used an ease other than
none(the speed changes across each pass, so the boundary pulses). Move by exactly one copy's width and setease: "none". - What's the difference between repeat: -1 and yoyo for a loop?
repeat: -1replays the same animation from the start every time, which is what a marquee needs (content keeps travelling one direction).yoyo: truemakes each repeat run in reverse, so the content scrolls one way then back, a ping-pong. Use yoyo for a pulsing or breathing effect, not for a ticker or ribbon.- How do I pause a GSAP loop on hover?
- Keep a reference to the tween or timeline and call
loop.pause()onmouseenterandloop.play()onmouseleave. For a softer feel, animate the timeScale instead:gsap.to(loop, { timeScale: 0, duration: 0.4 })on enter and back to1on leave, so the marquee eases to a stop rather than freezing mid-motion. - Should I use a CSS animation or GSAP for a marquee?
- For a purely decorative, always-on scroll with no interaction, a CSS
@keyframestranslate loop is lighter and needs no JS. Reach for GSAP when you want drag-to-scrub, momentum, pause-on-hover, direction changes, or per-item wrapping, the kind of control the Infinite Marquee component ships. The seam-matching principle (matched start/end, linear timing) is identical in both.
