<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
This Pen doesn't use any external CSS resources.