A marquee that loops clean is two math problems
We built a logo bar for one of our sites. Client logos glide left, forever, the kind of feature you expect to finish before lunch. Ours glided for a few seconds, then jumped sideways about 10px at the loop point, every time. A hiccup you cannot unsee.
Every CSS marquee runs on the same trick. Duplicate the row of logos, animate the whole track left, and reset the animation at the exact moment copy 2 stands where copy 1 started. When the pixels line up, the reset is invisible. When they miss by even a few pixels, everyone sees it.
A marquee is a conveyor belt. The splice has to line up, and the belt has to be long enough that its end never crosses the window. Those are two separate problems. Either one alone breaks the illusion, and they fail in different ways.
Problem 1: move exactly one copy width
The animation must translate the track by the width of one full copy of the content. Not roughly. Exactly. For N unique logos, that distance is N times (tile width plus gap).
The tempting shortcut is translateX(-50%) on a doubled track. Skip it. Flex gap only exists between items, so there is no trailing gap after the last tile, and max-content sizing rounds differently across browsers. The halfway point lands a few pixels short of a true copy width, and those few pixels are the jitter.
Write the literal distance instead: calc(var(--tiles) * (var(--tile) + var(--gap)) * -1). Set the tile count to the number of unique logos, not the total after duplication. The math then holds no matter how the container sizes itself.
Problem 2: the tail must outlast the viewport
The second failure looks different. Logos glide left, the right edge runs out of content, blank space flashes, then the whole row snaps back. That is not loop math. That is too few copies.
After the track moves one copy width, what remains still has to cover the whole viewport. With K copies of N tiles at pitch P (tile plus gap), the leftover is (K - 1) times N times P, and it has to beat the viewport width. Solve for K: K must be at least 1 + viewport / (N times P).
Real numbers from our build: 7 logos at a 200px pitch is 1400px of content. Against a 1920px viewport, K comes out to 2.37, so 3 copies.
Against a 3840px ultrawide, 4. We default to 3. The duplicates cost nothing on the wire, since the browser reuses the same cached images.
Diagnose by symptom
A small jitter at the reset point, 10 to 15px of sideways jump, means problem 1. The translate distance missed a true copy width. Go measure your gap handling.
A stretch of blank space before a big snap means problem 2. Not enough copies for your widest supported screen. Add one.
Both at once reads as chaos. Fix the translate distance first, because you cannot judge coverage while the loop point itself is wrong.
One caveat. If a library like Swiper or GSAP drives the marquee, it handles both conditions internally and this math is its problem, not yours. The arithmetic here is for the pure CSS version, which weighs nothing and needs no dependency.
Two formulas, computed once, before the animation exists. Every logo bar we have shipped since gets those two numbers up front, and none of them hiccup. Do the math first. It is cheaper than staring at a 10px jump trying to will it away.