Search code examples
cssreactjsscrollcss-animationsinfinite-scroll

ReactJS Continuous looping animation scroll


I have a div with some children an I have scroll animation inside, this is my code :

const { useRef, useEffect, useState } = React;

function Card({ cardName, refCard }) {
  return (
    <div className="bubble" ref={refCard}>
      <div className="card m-2 pt-2">
        <div className="py-1">
          <div className="fs-5 mt-2">{cardName}</div>
        </div>
      </div>
    </div>
  );
}

let data = [
  {
    Name: "1"
  },
  {
    Name: "2"
  },

  {
    Name: "3"
  },

  {
    Name: "4"
  },

  {
    Name: "5"
  }
];

function App() {
  const ref = useRef(null);
  const [containerWidth, setWidth] = useState(100 + "%");
  const [animationState, setPlay] = useState("paused");
  useEffect(() => {
    if (ref.current) {
      setWidth(ref.current.scrollWidth + "px");
      setPlay("running");
    }
  }, []);
  console.log(containerWidth);
  const renderCards = data.map((el, index) => {
    return <Card key={index} cardName={el.Name} />;
  });

  return (
    <div className="App">
      <div
        className="d-flex"
        ref={ref}
        style={{
          width: `${containerWidth}`,
          animationPlayState: animationState
        }}
      >
        {renderCards}
        {renderCards}
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(<App/>);
.App {
  overflow: hidden;
}
.card {
  width: 200px !important;
  height: 200px;
  background: #ffefef;
  box-shadow: 0px 1px 4px 1px rgba(158, 151, 151, 0.25);
  border-radius: 15px;
  margin: 12px;
  padding: 12px;
}
.d-flex {
  overflow: visible;
  animation: animateContainer 5s linear forwards infinite;
}
@keyframes animateContainer {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="app"></div>

In this example I have only 5 children to display each 5s because of ( animation: animateContainer 5s linear forwards infinite;), but in case I have 100 childrens I can't see all items.. I've tried to change 5s to another variable but the speed change and it's so fast so I want to have the same speed, have you an idea how to achieve it ? I'm open if you have another idea to do it, not sure if the css animation will do the job


Solution

  • Speed = distance ÷ time. When there are more cards, this increases distance, so to keep the same speed, you'd want to increase time proportionally. Since you set 5 seconds in your example for 5 cards, then we could extrapolate this to say that the animation duration should be 1 second per card. You could set this as the animationDuration property on the style attribute, using the amount of cards that would render:

    const { useRef, useEffect, useState } = React;
    
    function Card({ cardName, refCard }) {
      return (
        <div className="bubble" ref={refCard}>
          <div className="card m-2 pt-2">
            <div className="py-1">
              <div className="fs-5 mt-2">{cardName}</div>
            </div>
          </div>
        </div>
      );
    }
    
    const data = Array(100).fill().map((_, i) => ({ Name: i }));
    
    function App() {
      const ref = useRef(null);
      const [containerWidth, setWidth] = useState(100 + "%");
      const [animationState, setPlay] = useState("paused");
      useEffect(() => {
        if (ref.current) {
          setWidth(ref.current.scrollWidth + "px");
          setPlay("running");
        }
      }, []);
      console.log(containerWidth);
      const renderCards = data.map((el, index) => {
        return <Card key={index} cardName={el.Name} />;
      });
    
      return (
        <div className="App">
          <div
            className="d-flex"
            ref={ref}
            style={{
              width: `${containerWidth}`,
              animationPlayState: animationState,
              animationDuration: `${data.length}s`,
            }}
          >
            {renderCards}
            {renderCards}
          </div>
        </div>
      );
    }
    
    ReactDOM.createRoot(document.getElementById('app')).render(<App/>);
    .App {
      overflow: hidden;
    }
    .card {
      width: 200px !important;
      height: 200px;
      background: #ffefef;
      box-shadow: 0px 1px 4px 1px rgba(158, 151, 151, 0.25);
      border-radius: 15px;
      margin: 12px;
      padding: 12px;
    }
    .d-flex {
      overflow: visible;
      animation: animateContainer 5s linear forwards infinite;
    }
    @keyframes animateContainer {
      from {
        transform: translateY(0);
      }
      to {
        transform: translateY(-50%);
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    
    <div id="app"></div>