Simple Marquee, No Library, Easy multiple Marquee Same Page

Just simple CSS, JS and HTML marquee.

You can duplicate as many marquee as you want on the same page easy to setup thanks to html attributes 🙂

Setup your html thats it.

<div class="marquee-container"
     data-text="White Label the Name, Custom Post Types, Custom Fields, Custom Taxonomies, Security Settings, Block Editor Settings, Disable File Editing, Disable WP JSON If Not Logged In"
     data-font-size="50"
     data-speed="20"
     data-direction="left">
</div>

<div class="marquee-container"
     data-text="SMTP Settings, Mail Logs, 404 Logs, Custom Login/Register Page, 301 Redirects, Remove WP Version, WP Revision Limit"
     data-font-size="50"
     data-speed="25"
     data-direction="right">
</div>


<style>
  .marquee-container {
    overflow: hidden;
    white-space: nowrap;
    margin-bottom: 20px;
  }
  .marquee {
    display: inline-block;
    white-space: nowrap;
  }
  .marquee-text {
    display: inline-block;
    white-space: nowrap;
    color: #00000044;
    line-height: 1;
    font-weight: 200;
  }
</style>


<script>
  class Marquee {
    constructor(container, settings) {
      this.container = container;
      this.settings = settings;
      this.animationStyleElement = null;
      this.init();
    }

    // Initialize the marquee: set font size and create the track element
    init() {
      this.container.style.fontSize = this.settings.fontSize + 'px';

      let marquee = this.container.querySelector('.marquee');
      if (!marquee) {
        marquee = document.createElement('div');
        marquee.className = 'marquee';
        this.container.appendChild(marquee);
      }
      this.marquee = marquee;
      this.setupMarquee();
    }

    // Setup the marquee by measuring text, duplicating content, and creating animation
    setupMarquee() {
      // Clear any existing content in the marquee track
      this.marquee.innerHTML = '';

      // Create a temporary element to measure the width of one copy of the text
      const tempSpan = document.createElement('span');
      tempSpan.className = 'marquee-text';
      tempSpan.style.visibility = 'hidden';
      tempSpan.style.position = 'absolute';
      tempSpan.textContent = this.settings.text;
      document.body.appendChild(tempSpan);
      const textWidth = tempSpan.getBoundingClientRect().width;
      document.body.removeChild(tempSpan);

      // Get the container width
      const containerWidth = this.container.getBoundingClientRect().width;

      // Calculate the number of copies needed for a seamless loop
      let copies = Math.ceil((containerWidth + textWidth) / textWidth);
      copies = Math.max(copies, 2); // Ensure at least two copies

      // Append copies of the text into the marquee track
      for (let i = 0; i < copies; i++) {
        const span = document.createElement('span');
        span.className = 'marquee-text';
        span.textContent = this.settings.text;
        this.marquee.appendChild(span);
      }

      // Determine the animation duration (in seconds)
      const duration = textWidth / this.settings.speed;

      // Create a unique animation name (useful for multiple marquees)
      const animationName = 'marqueeAnimation_' + Math.random().toString(36).substr(2, 9);

      // Create dynamic keyframes based on the direction
      let keyframes;
      if (this.settings.direction === 'left') {
        keyframes = `
            @keyframes ${animationName} {
              0% { transform: translateX(0); }
              100% { transform: translateX(-${textWidth}px); }
            }
          `;
        this.marquee.style.transform = 'translateX(0)';
      } else if (this.settings.direction === 'right') {
        keyframes = `
            @keyframes ${animationName} {
              0% { transform: translateX(-${textWidth}px); }
              100% { transform: translateX(0); }
            }
          `;
        this.marquee.style.transform = `translateX(-${textWidth}px)`;
      }

      // Remove any previous dynamic style element if it exists
      if (this.animationStyleElement) {
        this.animationStyleElement.remove();
      }

      // Insert the dynamic keyframes into a new style element in the document head
      this.animationStyleElement = document.createElement('style');
      this.animationStyleElement.innerHTML = keyframes;
      document.head.appendChild(this.animationStyleElement);

      // Force reflow to ensure the animation starts properly
      void this.marquee.offsetWidth;

      // Apply the animation to the marquee track
      this.marquee.style.animation = `${animationName} ${duration}s linear infinite`;
    }

    // Update the marquee (for example on window resize)
    update() {
      this.setupMarquee();
    }
  }

  document.addEventListener('DOMContentLoaded', function() {
    // Array to store marquee instances for later updates (e.g., on resize)
    const marqueeInstances = [];

    // Select all elements with the class "marquee-container"
    const containers = document.querySelectorAll('.marquee-container');

    containers.forEach(container => {
      // Retrieve settings from data attributes, with defaults if not provided
      const settings = {
        text: container.getAttribute('data-text') || '',
        fontSize: parseInt(container.getAttribute('data-font-size')) || 20,
        speed: parseFloat(container.getAttribute('data-speed')) || 20,
        direction: container.getAttribute('data-direction') || 'left'
      };

      marqueeInstances.push(new Marquee(container, settings));
    });

    // On window resize, update all marquees for responsiveness
    window.addEventListener('resize', () => {
      marqueeInstances.forEach(instance => instance.update());
    });
  });
</script>