Modern Marquees: how to make retro scrolling text in React
2024-02-19 by Gabriel C. Lins
In the realm of old websites where we left Shockwave, W3C Valid HTML badges and Perl, there once was the most beautiful of all DOM elements: the marquee.
When I think of the old web, many things immediately spring to mind: webmaster
emails on footers, image-based menus (screen readers be damned!), autoplaying
audio, custom cursors and many others. And although I miss most of these, I've
missed none more than the good old <marquee>
element.
Why you shouldn't use marquees
While supported by modern browsers for retrocompatibility, it is no longer part of web standards, so it could disappear at any point in the future, or be entirely absent from new engines that could pop up in the future. While you probably won't worry about that, it's still good to know there's a modern alternative you can use to achieve the same effect.
Why were they deprecated anyway?
Actually, it was never part of any HTML standard in any way. It was implemented in Internet Explorer back when it dominated the internet. As other browsers came along, they were obligated to offer some sort of support to this once popular element.
If you're wondering why it wasn't ever integrated into the standards, it's probably due to the then-conflicting and now-complementary roles of HTML and CSS on the web: HTML should be responsible only for markup (content), and CSS for the presentation.
How they usually tell you to achieve que marquee effect
If you look up any Stack Overflow question on the topic, or even the Wikipedia page on the element, you'll probably see a solution that looks like this:
@keyframes marquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
.marquee-container {
white-space: nowrap;
overflow: hidden;
position: relative;
animation: marquee 10s linear infinite;
}
.marquee-content {
display: inline-block;
}
And the result will look good enough:
While not that far from the solution I reached, it has a very specific problem: when trying to render content that is wider than the space it's supposed to roll within, things get wonky, because the content will only roll so far as the width of its container. Note how reading the full text below is impossible:
The fix for that is pretty simple, although it took me a lot of trial and error
to find: we make the marquee adjust its margin down from 100% in addition to the
transform. The reason this works is that instead of moving the content 100% of
its own width to the left, margin-left: 100%
is relative to the parent's width
instead. Notice how we change the initial transform from translateX(100%)
to
translateX(0)
:
@keyframes marquee {
0% {
margin-left: 100%;
transform: translateX(0);
}
100% {
margin-left: 0;
transform: translateX(-100%);
}
}
The result now scrolls from the start to the very end of the content as intended. Obviously, this is too much text for a marquee, so we would have to slow it down a lot and it still wouldn't be great for readability, so it's here just for the sake of the example:
I actually had to make this fix for the "now playing" component you can see on the bottom right corner of this page – whenever a song or artist with a lengthy name would show up there, the marquee wouldn't scroll until the end.
With all of these things considered, let's make our React element that is easily tweakable.
Our React <Marquee>
In this example we'll be using inline styles to make it easily copiable. You can easily convert this to Styled Components, Tailwind, Emotion or your favourite styling engine.
Start by making a simple component with the required styles:
function Marquee({ children }: PropsWithChildren) {
return (
<div style={{ overflow: 'hidden' }}>
<span style={{
display: 'inline-block',
whiteSpace: 'nowrap',
width: 'max-content',
animation: 'marquee 10s linear infinite',
}}>
{children}
</span>
</div>
)
}
Then create the animation keyframes in CSS:
@keyframes marquee {
0% {
margin-left: 100%;
transform: translateX(0);
}
100% {
margin-left: 0;
transform: translateX(-100%);
}
}
Now you can easily create marquees, but we still have the aforementioned problem of not being able to control its speed.
For that, we can simply add a prop for animation speed with a default value:
function Marquee({
children,
animationDuration = '10s'
}: PropsWithChildren<{ animationDuration?: string }>) {
return (
<div style={{ overflow: 'hidden' }}>
<span style={{
display: 'inline-block',
whiteSpace: 'nowrap',
animation: 'marquee 10s linear infinite',
animationDuration,
}}>
{children}
</span>
</div>
)
}
Now if you have something running too quick, you can easily make the animation last longer so people have time to read your content.
Accessibility considerations
Even though we're probably using this in retro-looking sites, we still live in the modern world, where marquee and other effects have very little place considering all different devices, screens and different people that will be accessing our content. While I do use marquees in this site for the fun of it, I still have to make sure that no essential content is moving.
If you're planning on making some essential content of your page move indefinitely, such as a heading, an image that is integral to the content, or something the user is supposed to interact with, then you're probably approaching it the wrong way. Usability always beats eye candy.
How are you going to use marquees?
These days I really enjoy bringing back these vibes from the simpler times. Marquees are definitely an easy way to evoke this feeling.
My favourite use is probably simulating the car radio/MP3 player LCD screen: it gives off this very specific vibe that reminds us of listening to music in simpler devices with low-resolution screens. Not only LCDs, but even computers, DVD systems and iPods back in the day used to use rolling text to display song and video names because they were occasionally too long to show all at once.
Other uses include decorative images (if you're going for that sweet Geocities aesthetic), very slow background movement to create dynamic scenes, and non-interactive image carousels. Now go and be creative!