Infinite Marquee Text Scroll

A seamless, infinite scrolling text marquee component. Features duplicate text layers for a continuous loop, pause on hover, variable speed controls, bi-directional support, and elegant gradient fade edges.


Infinite Marquee Text Scroll


Create a captivating, seamless scrolling text effect perfect for announcements, partners lists, or news tickers. This snippet uses pure CSS for the animation and gradient masks, ensuring high performance and smooth motion.

Features

HTML Structure

The structure requires a wrapper for the mask/overflow and a track for the animation. Inside the track, place your content twice (or more) to ensure there is no gap when the animation resets.

<div class="marquee-container">
  <div class="marquee-track">
    <!-- Content Group 1 -->
    <div class="marquee-content">
      <span>BREAKING NEWS</span>
      <span class="separator"></span>
      <span>EXCLUSIVE OFFERS</span>
      <span class="separator"></span>
      <span>LATEST UPDATES</span>
      <span class="separator"></span>
      <span>SIGN UP NOW</span>
      <span class="separator"></span>
    </div>

    <!-- Content Group 2 (Duplicate of Group 1) -->
    <div class="marquee-content" aria-hidden="true">
      <span>BREAKING NEWS</span>
      <span class="separator"></span>
      <span>EXCLUSIVE OFFERS</span>
      <span class="separator"></span>
      <span>LATEST UPDATES</span>
      <span class="separator"></span>
      <span>SIGN UP NOW</span>
      <span class="separator"></span>
    </div>
  </div>
</div>

CSS Styling

We use CSS variables for easy configuration. The mask-image property creates the soft fade effect on the left and right edges. The animation simply translates the track.

For the Reverse Direction (Right-to-Left), simply change --direction to reverse or create a modifier class.

:root {
  --marquee-speed: 20s;
  --marquee-gap: 2rem;
  --marquee-text-color: #ffffff;
  --marquee-bg: #111;
  --fade-width: 100px;
}

body {
  background-color: #050505;
  color: #fff;
  font-family: 'Inter', sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 200px;
  margin: 0;
}

.marquee-container {
  position: relative;
  width: 100%;
  max-width: 800px; /* Adjust as needed */
  background: var(--marquee-bg);
  overflow: hidden;
  padding: 1rem 0;
  
  /* Gradient Fade Edges */
  mask-image: linear-gradient(
    to right,
    transparent 0%,
    black var(--fade-width),
    black calc(100% - var(--fade-width)),
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0%,
    black var(--fade-width),
    black calc(100% - var(--fade-width)),
    transparent 100%
  );
}

.marquee-track {
  display: flex;
  width: max-content;
  gap: var(--marquee-gap);
  animation: scroll var(--marquee-speed) linear infinite;
}

/* Pause on Hover */
.marquee-container:hover .marquee-track {
  animation-play-state: paused;
}

.marquee-content {
  display: flex;
  align-items: center;
  gap: var(--marquee-gap);
  font-size: 2rem;
  font-weight: 700;
  white-space: nowrap;
  color: var(--marquee-text-color);
  text-transform: uppercase;
}

.separator {
  color: #555; /* Dimmer separator */
  font-size: 1.5rem;
}

/* Keyframes */
@keyframes scroll {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(calc(-50% - (var(--marquee-gap) / 2)));
  }
}

/* Modifiers */
.marquee-track.reverse {
  animation-direction: reverse;
}

.marquee-track.fast {
  --marquee-speed: 10s;
}

.marquee-track.slow {
  --marquee-speed: 40s;
}

JavaScript (Optional Auto-Duplication)

If you don’t want to manually duplicate the HTML content, you can use this simple script to automatically clone the content for the seamless loop.

document.addEventListener('DOMContentLoaded', () => {
  const track = document.querySelector('.marquee-track');
  const content = track.querySelector('.marquee-content');
  
  // Clone the content
  const clone = content.cloneNode(true);
  clone.setAttribute('aria-hidden', 'true'); // Accessibility
  track.appendChild(clone);
});