Skip to content

Marquee text effect. Same scrolling speed no matter the length of the text

I have a element on my website similar to a marquee in that it slides from the right side to the left side of the screen. The html looks like this:

<div class='text-scroller-container'>
    <p class='message'></p>
</div>

There can be many different messages inside the scroller. Some range from a single word to a full sentence.

The way I am handling the scrolling is by setting left: 100% and adding a transition: left 5s. Then setting the left to 0 within js.

The problem im facing now is that messages that are super short scroll very slowly while very long messages scroll fast because theyre all binded to the 5s transition duration.

Im sure there is a way to instead, calculate a speed relative to the elements offsetWidth so that it scrolls at roughly the same speed no matter what the length of the message is.

My initial solution for this was to instead use a setInterval/requestAnimationFrame and move the element 1px at a time until its width is completely off screen. However, i now need to improve performance on my webapp so I am switching back to using transitions.

Does anyone have experience with this?

Answer

This sounds more like an an animation, than a transition. Where a transition runs only once when a state changes, animation can loop forever, creating that marquee effect.

What you’ll need is an animation loop. You can do that with CSS Keyframes. With it you can specify a start and an end state, then loop those states infinitely.

Now the problem here is the speed. The speeds needs to be calculated. CSS can’t do that so we’ll need to add some JavaScript which will take care of that.

The calculation for the speed is amount of pixels per second * (width of message + container width). So the amount of distance travelled within a period of time times the distance. The bigger the message, the bigger the duration.

The example below shows three marquee’s, each with different messages of different lengths. JavaScript loops over each message, makes the calculation, and sets the animationDuration in milliseconds for each message.

/**
 * The speed in time (in milliseconds) of a single pixel.
 * Changing this value will change the speed.
 * @type {number}
 */
const timePerPixel = 20;

/**
 * Width of the container.
 * Hardcoded for simplicity' sake.
 * @type {number}
 */
const containerWidth = 200;

/**
 * Select all the messages
 * @type {NodeList}
 */
const messages = document.querySelectorAll('.message');

/**
 * For each message, calculate the duration based on the lenght of the message.  
 * Then set the animation-duration of the animation.
 */
messages.forEach(message => {
  const messageWidth = message.offsetWidth;
  const distance = messageWidth + containerWidth;
  const duration = timePerPixel * distance;

  message.style.animationDuration = `${duration}ms`;
});
.text-scroller-container {
  position: relative;
  width: 200px;
  height: 20px;
  border: 1px solid #d0d0d0;
  border-radius: 3px;
  background-color: #f0f0f0;
  overflow: hidden;
  margin-bottom: 10px;
}

.message {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  margin: 0;
  white-space: nowrap;
  
  /* Starting postition */
  transform: translate3d(100%, 0, 0);
  
  /* Animation settings */
  animation-name: marquee-animation;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

@keyframes marquee-animation {
  from {
    /* Start right out of view */
    transform: translate3d(100%, 0, 0);
  }
  
  to {
    /* Animate to the left of the container width */
    transform: translate3d(-200px, 0, 0);
  }
}
<div class='text-scroller-container'>
  <p class='message'>This is a sentence. I'm a long sentence.</p>
</div>

<div class='text-scroller-container'>
  <p class='message'>This is a short sentence.</p>
</div>

<div class='text-scroller-container'>
  <p class='message'>This is a very long sentence. This sentence is going to be the longest one of them all.</p>
</div>

If you’re looking for performant animations, then use the transform property instead of left. While changing left will repaint the entire page, transform will only re-render only the portion that is affected by the transformation.