Scroll Animation Resets after Sometime

clock icon

asked 3 months ago Asked

message

2 Answers

eye

2 Views

So I am making my own portfolio website. And I wanted to add scroll animation with technologies I know:

  • I am using tailwind.css with next.js
  • I have my own component on Scrool.jsx

But scrolling stops after 10-15 seconds, resets and starts all over. It doesnt work smooth.

Here is the code:

"use client";
import { motion } from "framer-motion";
import { FaAws, FaReact, FaPython } from "react-icons/fa";
import { SiNextdotjs, SiMongodb, SiDjango } from "react-icons/si";
import { DiHtml5, DiCss3, DiJavascript1 } from "react-icons/di";
import { RiTailwindCssFill } from "react-icons/ri";

const technologies = [
  { icon: <DiHtml5 />, name: "HTML" },
  { icon: <DiCss3 />, name: "CSS" },
  { icon: <RiTailwindCssFill />, name: "TailwindCSS" },
  { icon: <DiJavascript1 />, name: "JavaScript" },
  { icon: <FaReact />, name: "React.js" },
  { icon: <SiNextdotjs />, name: "Next.js" },
  { icon: <FaPython />, name: "Python" },
  { icon: <SiDjango />, name: "Django" },
  { icon: <SiMongodb />, name: "MongoDB" },
  { icon: <FaAws />, name: "AWS" },
];

const Scrool = () => {
  return (
    <section className="py-12">
      <div className="container mx-auto">
        <h2 className="text-3xl font-bold text-center mb-8">
          Technologies I Know
        </h2>
        <div className="relative overflow-hidden">
          <motion.div
            className="flex gap-2 whitespace-nowrap"
            initial={{ x: "100%" }}
            animate={{ x: "-100%" }}
            transition={{ duration: 20, ease: "linear", repeat: Infinity }}
          >
            {technologies.map((tech, index) => (
              <div
                key={index}
                className="flex flex-col items-center p-2 w-32 h-32 flex-shrink-0"
              >
                <div className="text-4xl text-accent">{tech.icon}</div>
                <span className="text-lg text-center">{tech.name}</span>
              </div>
            ))}
            {technologies.map((tech, index) => (
              <div
                key={index + technologies.length}
                className="flex flex-col items-center p-2 w-32 h-32 flex-shrink-0"
              >
                <div className="text-4xl text-accent">{tech.icon}</div>
                <span className="text-lg text-center">{tech.name}</span>
              </div>
            ))}
          </motion.div>
        </div>
      </div>
    </section>
  );
};

export default Scrool;

 

2 Answers

This is because by default the width of a child element is controlled by the parent container. So if you check motion.div in your browser developer tools, you'll see that its width is actually only the width of the parent container, and it's actually overflowing. So you animate x from 100% to -100% and you will actually see the overflowed content at the end of the animation.

To fix this, you can add w-fit to motion.div to allow the child element to determine the width of the parent element:

<motion.div className="flex w-fit gap-2 whitespace-nowrap">
  ...
</motion.div>

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>

Write your answer here

Top Questions