<div id="root"></div>
/* Looper styles */
@keyframes slideAnim {
  from {
    transform: translateX(0%);
  }
  to {
    transform: translateX(-100%);
  }
}

.looper {
  width: 100%;
  overflow: hidden;
}

.looper__innerList {
  display: flex;
  justify-content: center;
  width: fit-content;
}

.looper__innerList[data-animate="true"] .looper__listInstance {
  animation: slideAnim linear infinite;   
}

.looper__listInstance {
  display: flex;
  width: max-content;

  animation: none;
}


/* Example content styles */
:root {  
  --green: #59C3C3;
  --offwhite: #EBEBEB;
  --red: #e65a5e;
}
html {
  height: 100%;
}
body {
  height: 100%;
  display: flex;
  flex-direction: column;
}
#root {
  flex: 1;
  padding: 16px;
  font-family: sans-serif;
  color: var(--offwhite);  
  background: linear-gradient(-45deg, #4c468b, #05031a);  
}
.description {
  margin-bottom: 16px;  
  text-align: center;
}
.contentBlock {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 120px;
  margin: 10px;
  padding: 16px; 
  font-weight: 600;
  text-align: center;
  border-radius: 16px;
}
.contentBlock--one {  
  width: 120px;
  background: rgba(255, 255, 255, 0.05);    
  font-weight: 600;
  font-size: 18px; 
}
.contentBlock--one:last-of-type {
  color: var(--green);
  margin-right: 36px;
}
.contentBlock--two {    
  color: var(--green);  
  font-size: 48px;
  background: rgba(255, 255, 255, 0.05);  
}
const { useState, useEffect, useRef, useCallback } = React;

const InfiniteLooper = function InfiniteLooper({
  speed,
  direction,
  children,
}: {
  speed: number;
  direction: "right" | "left";
  children: React.ReactNode;
}) {
  const [looperInstances, setLooperInstances] = useState(1);
  const outerRef = useRef<HTMLDivElement>(null);
  const innerRef = useRef<HTMLDivElement>(null);

  function resetAnimation() {
    if (innerRef?.current) {
      innerRef.current.setAttribute("data-animate", "false");

      setTimeout(() => {
        if (innerRef?.current) {
          innerRef.current.setAttribute("data-animate", "true");
        }
      }, 10);
    }
  }

  const setupInstances = useCallback(() => {
    if (!innerRef?.current || !outerRef?.current) return;

    const { width } = innerRef.current.getBoundingClientRect();

    const { width: parentWidth } = outerRef.current.getBoundingClientRect();

    const widthDeficit = parentWidth - width;

    const instanceWidth = width / innerRef.current.children.length;

    if (widthDeficit) {
      setLooperInstances(
        looperInstances + Math.ceil(widthDeficit / instanceWidth) + 1
      );
    }

    resetAnimation();
  }, [looperInstances]);
  
  
  /*
    6 instances, 200 each = 1200
    parent = 1700
  */

  useEffect(() => setupInstances(), [setupInstances]);

  useEffect(() => {
    window.addEventListener("resize", setupInstances);

    return () => {
      window.removeEventListener("resize", setupInstances);
    };
  }, [looperInstances, setupInstances]);

  return (
    <div className="looper" ref={outerRef}>
      <div className="looper__innerList" ref={innerRef} data-animate="true">
        {[...Array(looperInstances)].map((_, ind) => (
          <div
            key={ind}
            className="looper__listInstance"
            style={{
              animationDuration: `${speed}s`,
              animationDirection: direction === "right" ? "reverse" : "normal",
            }}
          >
            {children}
          </div>
        ))}
      </div>
    </div>
  );
}

const App = () => (
  <div className="app">
    <p className="description">
      Just throw some content into the component and set the speed and
      direction.
    </p>

    <InfiniteLooper speed="4" direction="right">
      <div className="contentBlock contentBlock--one">
        Place the stuff you want to loop
      </div>
      <div className="contentBlock contentBlock--one">right here</div>
    </InfiniteLooper>

    <InfiniteLooper direction="right" speed="0.4">
      <div className="contentBlock contentBlock--two">
        <i>faster 🚀</i>
      </div>
    </InfiniteLooper>
  </div>
);

ReactDOM.render(<App />, document.getElementById("root"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js