<h1 style="text-align: center">Infinite Scroll Animation</h1>
<div class="main-container">

  <div class="scroller" data-speed="slow">
    <ul class="tag-list scroller__inner">
      <li>HTML</li>
      <li>CSS</li>
      <li>JS</li>
      <li>SSG</li>
      <li>Design</li>
      <li>Animation</li>
      <li>UI/UX</li>
    </ul>
  </div>
  <div class="scroller" data-speed="fast" data-direction="up">
    <ul class="tag-list scroller__inner">
      <li>HTML</li>
      <li>CSS</li>
      <li>JS</li>
      <li>SSG</li>
      <li>Design</li>
      <li>Animation</li>
      <li>UI/UX</li>
    </ul>
  </div>
</div>
/* general styles */

:root {
  --clr-neutral-100: hsl(0, 0%, 100%);
  --clr-primary-100: hsl(205, 15%, 58%);
  --clr-primary-400: hsl(215, 25%, 27%);
  --clr-primary-800: hsl(217, 33%, 17%);
  --clr-primary-900: hsl(218, 33%, 9%);
  --gap: 1rem;
}

html {
  color-scheme: dark;
}

body {
  display: grid;
  min-block-size: 100vh;
  place-content: center;
  font-family: system-ui;
  font-size: 1.125rem;
  background-color: var(--clr-primary-800);
}

.main-container {
  display: flex;
  gap: 24px;
  height: 400px;
}

/* elemets style */
.scroller {
  width: 400px;
  height: 100%;
  will-change: transform;
}

.tag-list {
  margin: 0;
  padding-inline: 0;
  margin: 0 auto;
  list-style: none;
}

.tag-list li {
  padding: 1.2rem;
  background: var(--clr-primary-400);
  margin-bottom: var(--gap);
  border-radius: 0.25rem;
  box-shadow: 0 0.5rem 1rem -0.25rem var(--clr-primary-900);
}

.scroller__inner {
  width: 100%;
  white-space: nowrap;
  will-change: transform;

  gap: var(--gap);
}

/* edges mask */
.scroller[data-animated="true"] {
  overflow: hidden;
  -webkit-mask: linear-gradient(
    0deg,
    transparent,
    white 20%,
    white 80%,
    transparent
  );
  mask: linear-gradient(0deg, transparent, white 20%, white 80%, transparent);
}

.scroller[data-animated="true"] .scroller__inner {
  animation: scroll var(--_animation-duration, 15s)
    var(--_animation-direction, forwards) linear infinite;
}

/* direction */
.scroller[data-direction="up"] {
  --_animation-direction: reverse;
}

.scroller[data-direction="down"] {
  --_animation-direction: forwards;
}

/* speed */
.scroller[data-speed="fast"] {
  --_animation-duration: 10s;
}

.scroller[data-speed="slow"] {
  --_animation-duration: 24s;
}

/* animation */
@keyframes scroll {
  from {
    transform: translateY(0);
  }

  to {
    transform: translateY(calc(-50% - var(--gap) / 2));
  }
}
const scrollers = document.querySelectorAll(".scroller");

// If a user hasn't opted in for recuded motion, then we add the animation
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
  addAnimation();
}

function addAnimation() {
  scrollers.forEach((scroller) => {
    // add data-animated="true" to every `.scroller` on the page
    scroller.setAttribute("data-animated", true);

    // Make an array from the elements within `.scroller-inner`
    const scrollerInner = scroller.querySelector(".scroller__inner");
    const scrollerContent = Array.from(scrollerInner.children);

    // For each item in the array, clone it
    // add aria-hidden to it
    // add it into the `.scroller-inner`
    scrollerContent.forEach((item) => {
      const duplicatedItem = item.cloneNode(true);
      duplicatedItem.setAttribute("aria-hidden", true);
      scrollerInner.appendChild(duplicatedItem);
    });
  });
}
Run Pen

External CSS

  1. https://codepen.io/kevinpowell/pen/BavZQjN/9451cb2bddcecda34df2a0f718703cbc.css

External JavaScript

This Pen doesn't use any external JavaScript resources.