JSFiddle - React, Tailwind, and code Playground

by HDL52

HTML

<div class="tag-scrollers">
  <div class="tag-scroller">
    <ul class="tag-list">
      <li>Git</li>
      <li>GitHub</li>
      <li>React</li>
      <li>Redux Toolkit</li>
      <li>Redis</li>
      <li>R3F</li>
      <li>Docker</li>
      <li>GraphQL</li>
      <li>Postman</li>
      <li>Unity</li>
      <li>Playwright</li>
      <li>Firebase</li>
      <li>Vite</li>
      <li>Vercel</li>
      <li>MySQL</li>
      <li>MongoDB</li>
      <li>Material UI</li>
      <li>NextUI</li>
      <li>Ant Design</li>
      <li>JavaScript</li>
      <li>TypeScript</li>
      <li>Python</li>
      <li>Java</li>
      <li>Ruby</li>
      <li>Next.js</li>
      <li>Vue.js</li>
      <li>Express</li>
      <li>ASP.NET</li>
    </ul>
  </div>
</div>

CSS

@import url('https://fonts.googleapis.com/css2?family=Concert+One&display=swap');

:root {
  --dark: #252A2E;
  --light: #F5F5F5;
  --background: var(--light);
  --text: var(--dark);
  --border: var(--dark);
}

@media (prefers-color-schema: dark) {
  :root {
    --background: var(--dark);
    --text: var(--light);
    --border: var(--light);
  }
}

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  display: grid;
  min-block-size: 100vh;
  place-content: center;
  background: var(--background);
  color: var(--text);
}

.tag-scrollers {
  width: 75%;
  max-width: 1200px;
  overflow: hidden;
  margin: auto;
}

.tag-scroller {
  display: grid;
  gap: 1.5rem;
  mask: linear-gradient(90deg, #0000, var(--background) 15%, var(--background) 85%, #0000);
}

.tag-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
}

@media(prefers-reduced-motion) {
  .tag-list {
    flex-flow: row nowrap;
    overflow: auto;
    scrollbar-width: none;
    scrollbar-color: transparent transparent;
  }

  .tag-list::-webkit-scrollbar-track {
    background: transparent;
  }

  .tag-list::-webkit-scrollbar-thumb {
    background: transparent;
  }

  .tag-list::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
  }
}

.tag-list li {
  font-family: "Concert One";
  font-size: 1rem;
  line-height: 1;
  padding: 0.625rem 1.375rem;
  border: 2px solid var(--border);
  border-radius: 10ch;
  white-space: nowrap;
}

.tag-scroller.scrolling .tag-list {
  width: max-content;
  flex-wrap: nowrap;
  animation: horizontal-scroll var(--duration) var(--direction, normal) linear infinite;
}

.tag-scroller.scrolling .tag-list:nth-child(even) {
  --direction: reverse;
}

.tag-scroller:hover .tag-list {
  /* animation-play-state: paused; */
}

@keyframes horizontal-scroll {
  to {
    /*	0.75rem FOR HALF THE GAP
				OF THE .tag-scroller
		*/
    transform: translateX(calc(-50% - .75rem));
  }
}

JavaScript

const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

if (mediaQuery && !mediaQuery.matches) {
  const tagScroller = document.querySelector(".tag-scroller");
  const allTags = tagScroller.querySelectorAll("li");

  const createElement = (tagName, className = "") => {
    const elem = document.createElement(tagName);
    elem.className = className;
    return elem;
  }

  const scrollersFrom = (elements, numColumns = 2) => {
    const fragment = new DocumentFragment();
    elements.forEach((element, i) => {
      const column = i % numColumns;
      const children = fragment.children;
      if (!children[column]) fragment.appendChild(createElement("ul", "tag-list"));
      children[column].appendChild(element);
    });
    return fragment;
  }

  const addScrolling = () => {
    tagScroller.classList.add("scrolling");
    document.querySelectorAll(".tag-list").forEach((tagList) => {
      const scrollContent = Array.from(tagList.children);
      scrollContent.forEach((listItem) => {
        const clonedItem = listItem.cloneNode(true);
        clonedItem.setAttribute("aria-hidden", true);
        tagList.appendChild(clonedItem);
      });
      tagList.style.setProperty("--duration", (tagList.clientWidth / 100) + "s");
    });
  }


  const scrollers = scrollersFrom(allTags, 2);
  tagScroller.innerHTML = "";
  tagScroller.appendChild(scrollers);
  addScrolling();
}