Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Jake Scott
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Jake Scott

Creating A Marquee Effect With CSS Animations

By
Published in Comments (9)

The other day, while using the Pandora web app, I noticed that long song titles continuously scrolled by in a classic marquee fashion. Upon inspecting the DOM (Document Object Model), I saw that this effect was created using two side-by-side copies of the same content. Way back in the day, I used to do the same thing for some clients; but, my technique was powered with JavaScript. Pandora, on the other hand, was powering it with CSS transitions. I thought this would be fun to try out for myself.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

The concept here is simple but clever. You place two copies of the same content side-by-side and then you transition both of them to the left at the same time using a @keyframes animation:

@keyframes marquee-content {
	/* Element one fully ON screen at left-edge of container. */
	from {
		transform: translateX( 0% );
	}
	/* Element one fully OFF screen (just beyond left-ledge of container). */
	to {
		transform: translateX( -100% );
	}
}

When the first element is at a -100% translation, it will have just moved fully off the screen. And, the second element will end up where the first element started. At that point, you snap both elements back to the starting location and it looks like one long continuous animation.

Here's the full code. Notice that the animation-iteration-count is infinite, which is what allows the animation to appear continuous. And, that the animation-timing-function is linear. This "timing" is important because it hides the fact that the elements are snapping around and gives the illusion of an infinite content tape.

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<style type="text/css">

		.marquee {
			border: 2px solid red ;
			display: flex ;
			overflow: hidden ;
			white-space: nowrap ;
			width: 300px ;
		}
		.marquee__item {
			animation-duration: 4s ;
			animation-iteration-count: infinite ;
			animation-name: marquee-content ;
			animation-timing-function: linear ;
			padding: 5px 15px 5px 15px ;
		}
		.marquee:hover .marquee__item {
			animation-play-state: paused ;
		}

		/**
		* BOTH of the marquee items are going to be translating left at the same time.
		* And, once each of them has translated to -100%, they will both snap back into
		* place, making it seem as if they are continuously scrolling.
		*/
		@keyframes marquee-content {
			from {
				transform: translateX( 0% );
			}
			to {
				transform: translateX( -100% );
			}
		}

	</style>
</head>
<body>

	<h1>
		Creating A Marquee Effect With CSS Animations
	</h1>

	<div class="marquee">
		<div class="marquee__item">
			There is quite a good deal of content over here.
		</div>
		<!--
			Duplication of the content in order to create a seamless wrapping simulation.
			As the element ABOVE heads off screen-left, the element BELOW will enter
			screen-right.
		-->
		<div class="marquee__item">
			There is quite a good deal of content over here.
		</div>
	</div>

</body>
</html>

And, when we run this in the browser, we get the following output:

GIF showing scrolling content in a marquee frame.

As you can see—or rather, as you can't see—the moment the first element is fully off screen, the CSS animation reaches its end and both elements snap back to their 0% offset. However, going from -100% to 0% doesn't create any visual change (that the user will notice).

Of course, this only makes sense if the piece of content is wider than the content container. Which means, if you wanted to implement something like this, you would either have to know that the length is long ahead of time; or, you would have to measure the content at runtime and dynamically duplicate the content if needed (and add CSS classes that would trigger the animation).

As I was putting this together, I came across a CSS Tricks post by Mads Stoumann that looks at pausing CSS animations. I didn't even know that this was possible! But, if you set the animation-play-state to paused, the CSS animation will stop in it current offset. In this demo, I am applying that state via the :hover pseudo-selector.

There's not a large number of places in which a marquee effect makes sense on the modern web. But, this was fun to build and I learned something new about CSS! Explorations like this are always a good - and fun - use of time.

Want to use code from this post? Check out the license.

Reader Comments

93 Comments

This could be nightmarish if used in abundance on a webpage that has lots of unintended truncated/overflowed content.

I think I'd switch it up and add a visual indicator on any elements that use this (due to overflow) and then scroll while hovering/mouseover and perhaps reset to original upon mouseexit. (Too much animation can be a distraction.)

To avoid being dinged by SEO for duplicate content, javascript could be used to autogenerate the duplicated DIVs.

By way of contrast, my preferred music playback app (PlexAmp) scrolls long titles back-and-forth on both desktop & mobile apps. (When it scrolls to the end of text, it pauses and then reverses direction.) This approach wouldn't require duplicating any content. I personally prefer this approach and it's possible that it could be done with more complex animation keyframes (instead of using JS).

16,254 Comments

@James,

Too much animation can be a distraction

You are preaching to the choir. I actually heavily dislike most animations. And will gladly remove most of them. In fact, in the cast of Pandora, I'd rather just let the long line wrap - that way I can still the full value at a glance. Most designers tend to not want things to wrap as it "breaks" the neat-and-tidy UI they've designed.

That said, I just liked the fact that stuff that used to be in JavaScript can now be (at least mostly) in CSS in some cases. What an exciting time to be alive 😆

I do like the idea of having it not animate until you hover over it. That also saves on the GPU cost, since animations are "running" even when when the page isn't focused, I think.

1 Comments

Fun trick! One thing to note is that the animation-duration: 4s ; declaration will render a different speed depending on the length of the string.

16,254 Comments

@Bruno,

Ahhh, that's a great point that I had not considered.

For anyone else reading this, the reason that string length changes the animation speed has to do with the fact that the elements on the page are translating 100% of their width over a fixed amount of time.

So, for example, if a string creates a Div that is 100px long and the horizontal animation is -100% over 4s, that's -25px per second.

But, if another string creates a Div that is 200px long. The same animation over 4s is now only -50px per second. Essentially, the string that is 2x as long has to move 2x the distance in the same amount of time.

Spot-on, Bruno! 🙌

1 Comments

Hello! I know this article is a bit old but I hope you still respond because I am stuck :(.

Okay, so at the top of the page linked, I added a cute little marquee of all of my favorite characters. At first, the marquee consisted of 16 square images. I'm not particularly sure of each image's width but I know their max-height is 136px.

The first problem I ran into is: The container of content doesn't get bigger when you add more stuff? The marquee item container stays the same width, which really breaks the marquee because it restarts to early and looks super glitched out. Also, the user doesn't see all of the images. Although it was disappointing, I deleted 9 of the images from the marquee so it would continue to loop normally. However, it didn't loop normally and their was a bit of extra space before it started over.

I assumed this problem would be solved once I duplicated the container, but instead I ran into my second problem: the two containers overlap quite unpleasantly. If you happen to see this comment before I change the marquee, you will see what I mean with the second problem.

Do you have any advice?

(also, i'm a beginner and honestly have no clue how to insert the license so I put it between my end body tag and the end html tag. i hope this is okay and if you have any suggestions on that, that would be nice also!!)

16,254 Comments

@Yappy,

Looks like you got it figured out in the time since you posted 🙌 Welcome to the wonderful world of web development. This stuff is super fun; and when you see what's in your head come out on the screen, there's no feeling like it 🎉

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel
Managed ColdFusion hosting services provided by:
xByte Cloud Logo