Как сделать бесконечную анимацию с GSAP

К сожалению не могу понять как сделать бесконечное прокручивание списка элементов с помощью библиотеки GSAP 3. Сам принцип понятен, что нужно каждой плитке делать смещение, но не понятно как его сделать с помощью библиотеки GSAP 3. Суть задания состоит в том, что нужно нажать на некую кнопку, чтоб запустить механизм бесконечного вращения всего списка элементов и в одну сторону, а через некоторое время плавно остановиться на рандомной плитке. Сейчас список элементов вращается в обе стороны и последняя плитка и с отображением пустоты в конце и вначале списка. Прикрепляю свой пример реализациина codepen.

import React, {
  Suspense,
  useEffect,
  useRef,
  useState
} from "https://cdn.skypack.dev/react";
import ReactDOM from "https://cdn.skypack.dev/react-dom";

const items = [...Array(20).keys()];

interface HandleClickParams {
  nodeGamesSection: HTMLDivElement | null;
  nodeGamesGrid: HTMLUListElement | null;
  nodeGameGridItems: HTMLLIElement[];
}

function getRandomInt(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getPositionOfWinner({
  nodeGamesSection,
  nodeGameGridItems,
  winner
}: Pick<HandleClickParams, "nodeGamesSection" | "nodeGameGridItems"> & {
  winner: number;
}) {
  const margin = 12;
  const center = nodeGamesSection!?.offsetWidth / 2; // получаем центр контейнера
  const widthOfImg = nodeGameGridItems[winner]!?.offsetWidth / 2; // получаем центр плитки
  const left = nodeGameGridItems[winner]!?.offsetLeft - margin; // получаем позицию плитки по x

  return left - center - widthOfImg;
}

const handleClick = ({
  nodeGamesGrid,
  nodeGamesSection,
  nodeGameGridItems
}: HandleClickParams) => {
  if (nodeGamesGrid === null || nodeGamesSection === null) return;
  const winner = getRandomInt(0, nodeGameGridItems.length - 1);

  TweenMax.to(nodeGameGridItems, 5, {
    x:
      getPositionOfWinner({
        winner,
        nodeGameGridItems,
        nodeGamesSection
      }) * -1,
    ease: Power4.easeOut
  });
};

function App() {
  const refGamesSection = useRef<HTMLDivElement | null>(null);
  const refGamesGrid = useRef<HTMLUListElement | null>(null);
  const refGameGridItems = useRef<HTMLLIElement[]>([]);

  useEffect(() => {
    return () => {
      refGameGridItems.current = [];
    };
  }, []);

  return (
    <div className="container my-5">
      <div className="d-flex justify-content-center">
        <button
          type="button"
          class="btn btn-secondary arrow"
          onClick={() => {
            handleClick({
              nodeGamesSection: refGamesSection.current,
              nodeGamesGrid: refGamesGrid.current,
              nodeGameGridItems: refGameGridItems.current
            });
          }}
        >
          Start
        </button>
      </div>
      <div className="section">
        <div className="ul-container" ref={refGamesSection}>
          <ul ref={refGamesGrid}>
            {items.map(function (item) {
              return (
                <li
                  key={item}
                  ref={(element: HTMLLIElement) =>
                    refGameGridItems.current.push(element)
                  }
                >
                  <img
                    src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_17810fa9800%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_17810fa9800%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2299.1171875%22%20y%3D%2296.3%22%3EImage%20cap%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E"
                    alt="Card image cap"
                  />
                  <p>Image item {item}</p>
                </li>
              );
            })}
          </ul>
        </div>
      </div>
    </div>
  );
}

ReactDOM.render(
  <React.StrictMode>
    <Suspense fallback={() => <h1>Loading...</h1>}>
      <App />
    </Suspense>
  </React.StrictMode>,
  document.getElementById("root")
);

Сейчас работает вращение списка в обе стороны и с отображением пустоты


Ответы (0 шт):