But this brings a new problem: because the child element is very long, the position of the initial state 100% is actually very far away from the viewport, causing it to take some time before the element is visible after starting the animation.
To solve this problem:
1. Use useRef
Use useRef to get the actual width of motion.div and its parent div (by ref.current?.clientWidth), then animate x from -widthOfDiv to widthOfMotionDiv.
But useRef is initialized after motion, so this is actually very cumbersome and I won't expand on it here.
2. Use absolute
By using an absolute layout and animating translateX and left at the same time, it's easy to move an element completely from right to left. The downside is that you need to specify the height of the parent container in advance:
function SkillList() {
const technologies = [
{ icon: <div className="h-16 w-16 bg-red-400" />, name: "HTML" },
{ icon: <div className="h-16 w-16 bg-orange-400" />, name: "CSS" },
{ icon: <div className="h-16 w-16 bg-amber-400" />, name: "TailwindCSS" },
{ icon: <div className="h-16 w-16 bg-yellow-400" />, name: "JavaScript" },
{ icon: <div className="h-16 w-16 bg-lime-400" />, name: "React.js" },
{ icon: <div className="h-16 w-16 bg-green-400" />, name: "Next.js" },
{ icon: <div className="h-16 w-16 bg-emerald-400" />, name: "Python" },
{ icon: <div className="h-16 w-16 bg-teal-400" />, name: "Django" },
{ icon: <div className="h-16 w-16 bg-cyan-400" />, name: "MongoDB" },
{ icon: <div className="h-16 w-16 bg-sky-400" />, name: "AWS" },
];
const motion = window['Motion'].motion;
return (
<div className="max-w-5xl mx-auto">
<section className="py-12">
<div className="container mx-auto">
<h2 className="mb-8 text-center text-3xl font-bold">
Technologies I Know
</h2>
<div className="relative h-32 overflow-hidden">
<motion.div
className="absolute flex w-fit gap-2 whitespace-nowrap"
initial={{ x: "0%", left: "100%" }}
animate={{ x: "-100%", left: "0%" }}
transition={{ duration: 10, ease: "linear", repeat: Infinity }}
>
{technologies.map((tech, index) => (
<div
key={index}
className="flex h-32 w-32 flex-shrink-0 flex-col items-center p-2"
>
<div className="text-accent text-4xl">{tech.icon}</div>
<span className="text-center text-lg">{tech.name}</span>
</div>
))}
{technologies.map((tech, index) => (
<div
key={index + technologies.length}
className="flex h-32 w-32 flex-shrink-0 flex-col items-center p-2"
>
<div className="text-accent text-4xl">{tech.icon}</div>
<span className="text-center text-lg">{tech.name}</span>
</div>
))}
</motion.div>
</div>
</div>
</section>
</div>
);
}
ReactDOM.createRoot(document.getElementById('app')).render(<SkillList/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha512-QVs8Lo43F9lSuBykadDb0oSXDL/BbZ588urWVCRwSIoewQv/Ewg1f84mK3U790bZ0FfhFa1YSQUmIhG+pIRKeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha512-6a1107rTlA4gYpgHAqbwLAtxmWipBdJFcq8y5S/aTge3Bp+VAklABm2LO+Kg51vOWR9JMZq1Ovjl5tpluNpTeQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.5"></script>
<script src="https://cdn.jsdelivr.net/npm/framer-motion@11.3.8/dist/framer-motion.min.js"></script>
<div id="app"></div>
3. Infinite scrolling
Infinite scrolling changes the end result from full right-to-left scrolling, but it's still visually appealing. You just need to make sure that the list width is greater than the container width:
function SkillList() {
const motion = window['Motion'].motion;
return (
<div className="mx-auto max-w-5xl">
<section className="py-12">
<div className="container mx-auto">
<h2 className="mb-8 text-center text-3xl font-bold">
Technologies I Know
</h2>
<div className="overflow-hidden">
<motion.div
className="relative w-fit"
initial={{ x: "0%" }}
animate={{ x: "-100%" }}
transition={{ duration: 5, ease: "linear", repeat: Infinity }}
>
<List />
<div className="absolute left-full top-0">
<List />
</div>
</motion.div>
</div>
</div>
</section>
</div>
);
}
function List() {
const technologies = [
{ icon: <div className="h-16 w-16 bg-red-400" />, name: "HTML" },
{ icon: <div className="h-16 w-16 bg-orange-400" />, name: "CSS" },
{ icon: <div className="h-16 w-16 bg-amber-400" />, name: "TailwindCSS" },
{ icon: <div className="h-16 w-16 bg-yellow-400" />, name: "JavaScript" },
{ icon: <div className="h-16 w-16 bg-lime-400" />, name: "React.js" },
{ icon: <div className="h-16 w-16 bg-green-400" />, name: "Next.js" },
{ icon: <div className="h-16 w-16 bg-emerald-400" />, name: "Python" },
{ icon: <div className="h-16 w-16 bg-teal-400" />, name: "Django" },
{ icon: <div className="h-16 w-16 bg-cyan-400" />, name: "MongoDB" },
{ icon: <div className="h-16 w-16 bg-sky-400" />, name: "AWS" },
];
return (
<div className="flex gap-2 whitespace-nowrap">
{technologies.map((tech, index) => (
<div
key={index}
className="flex h-32 w-32 flex-shrink-0 flex-col items-center p-2"
>
<div className="text-accent text-4xl">{tech.icon}</div>
<span className="text-center text-lg">{tech.name}</span>
</div>
))}
</div>
);
}
ReactDOM.createRoot(document.getElementById('app')).render(<SkillList/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha512-QVs8Lo43F9lSuBykadDb0oSXDL/BbZ588urWVCRwSIoewQv/Ewg1f84mK3U790bZ0FfhFa1YSQUmIhG+pIRKeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha512-6a1107rTlA4gYpgHAqbwLAtxmWipBdJFcq8y5S/aTge3Bp+VAklABm2LO+Kg51vOWR9JMZq1Ovjl5tpluNpTeQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.5"></script>
<script src="https://cdn.jsdelivr.net/npm/framer-motion@11.3.8/dist/framer-motion.min.js"></script>
<div id="app"></div>