ASCII
DISCO
   __       __     \_\  __          __   \_\  __   __       __    
   \_\     /_/        \/_/         /_/      \/_/   \_\     /_/    
  .-.  \.-./  .-.   .-./  .-.   .-./  .-.   .-\   .-.  \.-./  .-. 
_//-\\_//-\\_//-\\_//-\\_//-\\_//-\\_// \\_//-\\_//-\\_//-\\_//-\\
  \__'-'   '-'\  '-'   '-'  /'-'   '-'\__'-'   '-'__/'-'   '-'\__ 
   \_\         \__       __/\          \_\       /_/           \_\
                \_\     /_/  \__                                  
                              \_\                                 

Continuous, infinite scrolling with wrap-around in HTML/CSS/JS

Posted on

This is one of those things that seem really simple on paper, but are weirdly complex to actually implement.

Good ol' marquee

Let's start with the simplest possible marquee tag.

<marquee>Hello, World</marquee>
Hello, World

We can try repeating the content -

<marquee>
    Hello, World
    Hello, World
</marquee>
Hello, World Hello, World
- but it doesnt't help much. The text wrapping is not continuous.

Let's fix marquee

This is where things get hairy. marquee by default, doesn't have a way to make text wrapping continuous. We must re-implement the marquee tag in some custom code.

Basic markup-

<style>
    #outer {
        border: 2px solid red;
    }

    #outer div {
        display: inline-block;
    }

    #loop {
        white-space: nowrap;
    }
</style>

<div id="outer">
    <!-- This div is important! It lets us specify margin-left as percentage later on. -->
    <div>
        <div id="loop"><div id="content">Hello, World&nbsp;</div></div>
    </div>
</div>
Hello, World 

First, we repeat the innerHTML of the content till it "fills up" at least the width of the outer container. Doing this in JS, as opposed to manually writing the text a bunch of times makes this code responsive to all screen sizes.

let outer = document.querySelector("#outer")
let content = document.querySelector("#content");

repeatContent(content, outer.offsetWidth);

repeatContent takes any DOM element and repeats it's innerHTML till it reaches a desired cut-off

function repeatContent(el, till) {
    let html = el.innerHTML;
    let counter= 0; // prevents infinite loop

    while (el.offsetWidth < till && counter < 100) {
        el.innerHTML += html;
        counter += 1;
    }
}
Hello, World 

Next, we add some animation using CSS

#loop {
    animation: loop-anim 5s linear infinite;
}

@keyframes loop-anim {
    0% {
        margin-left: 0;
    }
    100% {
        margin-left: -50% /* This works because of the div between "outer" and "loop" */
    }
}
Hello, World 

We also need to repeat the content itself, twice. so everything looks continuous, and there are no empty spaces in the animation. JS comes in handy here -

let el = outer.querySelector('#loop');
el.innerHTML = el.innerHTML + el.innerHTML;
Hello, World 

Add some colours to visualize what's actually going on here -

<style>
    #loop div:nth-child(1) {
        background: rgba(154, 255, 102, 0.2);
    }

    #loop div:nth-child(2) {
        background: rgba(123, 235, 255, 0.2);
    }
</style>
Hello, World! 

Here's what it looks like without any of the styling

Hello, World! 

Perfect, right?! This is the same technique used on this blog to show the beautiful moving ASCII art :). Full code can be found in this Stack Overflow answer.