skip to Main Content

I have a client of mine that’s using a marquee, I know, I know it’s a depreciated tag and frankly shouldn’t be used. This is a personal website of his, and he’s been a client of mine for a while, so I figured I’d implement the marquee but do so in a modern way without using depreciated tags. I have seen lots of different solutions for this. I generally use flexbox for something like this because it doesn’t rely on javascript or jQuery, but flexbox marquee examples often rely on a hard-coded width to calculate the speed of keyframe animations.

So, long story short, I am seeking to create a marquee that’s responsive, but the speed doesn’t change based on how much content it’s scrolling (I know it’s alot to ask). My client also writes a ton of content (I’ve tried to convince him otherwise, but he still does it).

I put something together that’s very close to being a solution. It doesn’t use flexbox, but it does resize well without changing the speed…I’m getting pretty close to what I’m after. At the end of the day I don’t have to use flexbox, so if all the other boxes are checked I’m good with that!

This is the code I’m using:

<div class="pizza">
    <p class="duck"><span class="red">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span> <span class="green">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span> <span class="blue">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>.</p>
</div>

The CSS:

.green {
    color: green;
}

.red {
    color: red;
}

.blue {
    color: blue;
}

.pizza {
    line-height: 60px;
    font-size: 16px;
    overflow: hidden;
    position: relative;
    white-space: nowrap;
}

p.duck {
  animation: scroll-left 20s linear infinite;
}

@keyframes scroll-left {
  0% {
    transform: translate(0, 0);
  }
  100% {
    transform: translate(-100%, 0);
  }
}

p.duck:hover {
    animation-play-state: paused;
}

Fiddle: https://jsfiddle.net/L7kbhyg6/

I added some spans so the problem is easier to see, but basically not all of the text is scrolling. I see red, then green, then the marquee jumps and it starts over again. Not sure how to get it to display all of my content.

Thanks,
Josh

3

Answers


  1. Chosen as BEST ANSWER

    I did some additional research and found an example using the gsap JS library.

    CSS:

    .green { color: green; }
    .red   { color: red;   }
    .blue  { color: blue;  }
    
    #pizza {
        align-items: center;
        display: flex;
        height: 40px;
    }
    
    #pizza .wrap {
        overflow: hidden;
        white-space: nowrap;
        width: 100%;
    }
    
    #pizza .content {
        display: inline-block;
        white-space: nowrap;
    }
    
    /* Media */
    @media screen and (min-width: 1000px) {
        #pizza {
            justify-content: center;
        }
    }
    

    HTML:

    <div id="pizza">
        <div class="wrap">
            <div class="content">
                <span class="red">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
                <span class="green">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
                <span class="blue">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>.
            </div>
        </div>
    </div>
    

    JS:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.2/gsap.min.js"></script>
    
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            const marqueeContent = document.querySelector("#pizza .content");
            const marqueeContainer = document.querySelector("#pizza .wrap");
            let marqueeAnimation;
    
            function updateMarquee() {
                const contentWidth = marqueeContent.scrollWidth;
                const containerWidth = marqueeContainer.clientWidth;
                const totalWidth = contentWidth + containerWidth;
                const speedFactor = 100; // Speed factor for faster scrolling
                const duration = totalWidth / speedFactor;
    
                // Kill any existing animation
                if (marqueeAnimation) {
                    marqueeAnimation.kill();
                }
    
                // Create a new animation
                marqueeAnimation = gsap.fromTo(marqueeContent, 
                    { x: containerWidth },
                    { x: -contentWidth, ease: "linear", repeat: -1, duration: duration }
                );
            }
    
            // Initial call
            updateMarquee();
    
            // Update the marquee on resize
            window.addEventListener("resize", updateMarquee);
    
            // Pause animation on hover
            marqueeContainer.addEventListener("mouseenter", () => gsap.globalTimeline.pause());
            marqueeContainer.addEventListener("mouseleave", () => gsap.globalTimeline.resume());
        });
    </script>
    

    This gives me the flexibility of adding or reducing the content without affecting the speed. This one is also responsive and restarts scrolling when the browser is resized.

    Thanks,
    Josh


  2. To ensure smooth scrolling regardless of viewport size or size of the scrolled elements, have a second copy.

    The transform is then to -50% not -100% (so the second copy gets to the left hand side then gets replaced by the first and so on, making it look continuous).

    I can’t see the need for JS unless you want to do something fancier with speed.

    UPDATE: OK, we want a constant speed whatever the client puts in the marquee.

    For this you do need a couple of lines of JS as CSS doesn’t ‘know’ the width of the overall text.

    This snippet gets CSS to calculate the overall length of time for one animation cycle. This depends on the speed you want the marquee to flow (a constant across all viewport sizes and amount of text) and the actual width of the overall marquee.

    The p.duck is set with flex, justify-content set so there is equal spacing between all elements, and a minimum width so even if the client goes down to a single character in each element the marquee will cover the viewport.

    function setUp() {
      const duck = document.querySelector('.duck');
      duck.style.setProperty('--w', window.getComputedStyle(duck).width.slice(0, -2));
    }
    window.onresize = setUp;
    setUp();
    .green {
      color: green;
    }
    
    .red {
      color: red;
    }
    
    .blue {
      color: blue;
    }
    
    .pizza {
      line-height: 60px;
      font-size: 16px;
      overflow: hidden;
      position: relative;
      white-space: nowrap;
    }
    
    p.duck {
      display: flex;
      justify-content: space-around;
      --s: 10s;
      /* time taken to travel  1000 px */
      animation: scroll-left calc(var(--s) * var(--w) / 2000) linear infinite;
      width: fit-content;
      min-width: 200vw;
    }
    
    @keyframes scroll-left {
      0% {
        transform: translate(0, 0);
      }
      100% {
        transform: translate(-50%, 0);
      }
    }
    
    p.duck:hover {
      animation-play-state: paused;
    }
    <div class="pizza">
      <p class="duck">
        <span class="red">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
        <span class="green">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
        <span class="blue">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
        <span class="red">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
        <span class="green">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
        <span class="blue">Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.</span>
      </p>
    </div>
    Login or Signup to reply.
  3. I think in this case using JS requestAnimationFrame would be the best way. –
    Mister Jojo

    Could you create an example, I’m not sure how to implement this solution.

    well here it is..
    [ Edit : changed to take care on screen resizing masking]
    [ Edit : first version with unique ID => code for only one "marquee" per page]

    (()=>  // IIFE closure  for <marquee> simulation on #pizza element
      {
      const
        pizza   = document.querySelector('#pizza')
      , pizza_p = document.querySelector('#pizza > p')
      , mov     = { pos   : pizza.offsetWidth
                  , max   : -pizza_p.offsetWidth
                  , pause : false 
                  };
      pizza.addEventListener('mouseenter', ()=>
        { 
        mov.pause = true; 
        });
      pizza.addEventListener('mouseleave', ()=>
        { 
        mov.pause = false; 
        requestAnimationFrame(MarqueeMov); 
        });
     
      // auto launch
      pizza_p.style.left = `${mov.pos}px`;
      requestAnimationFrame(MarqueeMov);
      
      function MarqueeMov()
        {
        pizza_p.style.left = `${--mov.pos}px`;
    
        if ( mov.pos < mov.max 
          || mov.pos > pizza.offsetWidth // added to take care of screen resize
          )
          mov.pos = pizza.offsetWidth;
    
        if (!mov.pause)
          requestAnimationFrame(MarqueeMov);
        }
      }
    )();
    .green { color: green; }
    .red   { color: red;   }
    .blue  { color: blue;  }
    
    #pizza {
      position    : relative;
      font-size   : 16px;
      height      : 32px;
      padding     : 0;
      overflow    : hidden;
      white-space : nowrap;
      cursor      : pointer;
      }
    #pizza > p {
      margin   : 0;
      position : absolute;
      bottom   : 0;
      left     : 0;
      }
    <div id="pizza">
      <p>
        <span class="red">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span> 
        <span class="green">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span>
        <span class="blue">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span>
      </p>
    </div>

    Second version, allowing you to manage several "horizontal marquee" fields on the same page

    (()=>  // IIFE closure  for <marquee> simulation for <div class="JS-Marquee"> elements
      {
      const
        jsMarquees = [...document.querySelectorAll('.JS-Marquee')].map( jsMx =>
          { 
          let txtP = jsMx.querySelector('p');
          return ({ boxElm  : jsMx
                  , boxLen  : jsMx.offsetWidth
                  , txtElm  : txtP 
                  , txtPEnd : -txtP.offsetWidth
                  , txtPos  : jsMx.offsetWidth
                  , pause   : false 
                  });
          });
      jsMarquees.actives = jsMarquees.length;
    
      window.addEventListener('resize', ()=>
        {
        jsMarquees.forEach( jsM => { jsM.boxLen = jsM.boxElm.offsetWidth });
        });
    
      jsMarquees.forEach( jsM =>
        {
        jsM.boxElm.addEventListener('mouseenter',()=>
          {
          jsM.pause = true;
          --jsMarquees.actives;
          });
        jsM.boxElm.addEventListener('mouseleave',()=>
          {
          jsM.pause = false;
          ++jsMarquees.actives;
          if (jsMarquees.actives===1)
            requestAnimationFrame(MarqueeMoves); 
          });
        })
    
      // auto launch
      jsMarquees.forEach( jsM => { jsM.txtElm.style.left = `${jsM.txtPos}px`; })
      requestAnimationFrame(MarqueeMoves);
    
      function MarqueeMoves()
        {
        jsMarquees.forEach( jsM =>
          {
          if (jsM.pause) return;
    
          --jsM.txtPos;
    
          if ( jsM.txtPos > jsM.boxLen
            || jsM.txtPos < jsM.txtPEnd 
            ) 
            jsM.txtPos = jsM.boxLen;
    
          jsM.txtElm.style.left = `${jsM.txtPos}px`;
          });
    
        if (jsMarquees.actives)
          requestAnimationFrame(MarqueeMoves); 
        }
    
    })();  // IIFE end of <marquee> JS simulation
    .green { color: green; }
    .red   { color: red;   }
    .blue  { color: blue;  }
    
    .JS-Marquee {
      position    : relative;
      font-size   : 16px;
      height      : 32px;
      padding     : 0;
      overflow    : hidden;
      white-space : nowrap;
      cursor      : pointer;
      }
    .JS-Marquee > p {
      margin   : 0;
      position : absolute;
      bottom   : 0;
      left     : 0;
      }
    <div class="JS-Marquee">
      <p>
        <span class="red">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span> 
        <span class="green">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span>
        <span class="blue">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span>
      </p>
    </div>
    
    <div class="JS-Marquee">
      <p>
        <span class="green">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span> 
        <span class="blue">
          Start Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut sed placeat aperiam quod nostrum itaque blanditiis soluta! Aliquid fuga molestias aut magni, pariatur doloremque, voluptas ipsa nobis ipsum voluptatibus end.
        </span>
      </p>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search