[React] 라이브러리 없이 무한 슬라이드 만들기

·2024년 5월 14일
15

React

목록 보기
3/3
post-thumbnail

라이브러리를 사용하지 않고 자동 재생 무한 슬라이드 애니메이션을 만들어 보자.

스타일링 라이브러리는 tailwind-CSS를 사용하여 진행한다.

오른쪽에서 왼쪽으로 진행되는 무한 슬라이드를 구현할 데이터 배열에 translateX을 적용할 경우, 배열이 모두 이동하게 된다면 빈 여백을 표시하게 된다.

이를 채워주기 위해서는 해당 배열의 복사본을 연결시켜 무한 슬라이드를 구현하게 된다.

구현 하기에 두 가지 방법을 고안할 수있다.

  1. 두 개의 배열, 두 개의 애니메이션 : 데이터 배열을 복사본을 만들어, 원본과 복사본의 각각 애니메이션을 적용
  2. 하나의 배열, 하나의 애니메이션 : 데이터 배열을 원본과 복사본을 연결시켜 하나의 배열로 만들어 하나의 애니메이션을 적용

아래의 이미지에서 슬라이드 데이터는 임의로 6개로 지정한다.

1. 두 개의 배열, 두 개의 애니메이션

원본과 복사본으로 두 개의 배열을 만들고, 각각의 애니메이션을 적용한다.

  • 원본 애니메이션: 원본 배열을 처음 보여주고 translateX(-100%)로 이동한다.
  • 복사본 애니메이션: 복사본은 원본과 복사본의 길이를 이동해야하기 떄문에 translateX(0%)에서 translateX(-200%)로 이동한다.
  • 이 방식은 각 배열이 독립적으로 애니메이션되어 원본이 끝나면 복사본이 시작된다.

tailwind-CSS를 적용하면 아래와 같다.

module.exports = {
  theme: {
    extend: {
      keyframes: {
        infiniteSlideOriginal: {
          "0%": { transform: "translateX(0)" },
          "50%": { transform: "translateX(-100%)" },
          "50.1%": { transform: "translateX(100%)" },
          "100%": { transform: "translateX(0)" },
        },
        infiniteSlideCopy: {
          "0%": { transform: "translateX(0)" },
          "100%": { transform: "translateX(-200%)" },
        },
      },
      animation: {
        infiniteSlideOriginal: "infiniteSlideOriginal 20s linear infinite",
        infiniteSlideCopy: "infiniteSlideCopy 20s linear infinite",
      },
    },
  },
};

2. 하나의 배열, 하나의 애니메이션

원본 배열과 복사본 배열을 하나로 합쳐 하나의 애니메이션을 적용한다.

이 방식은 단일 애니메이션으로 전체 슬라이드가 이동하며, translateX(-50%)로 이동한다.

module.exports = {
  theme: {
    extend: {
      keyframes: {
        infiniteSlide: {
          "0%": { transform: "translateX(0)" },
          "100%": { transform: "translateX(-50%)" },
        },
      },
      animation: {
        infiniteSlide: "infiniteSlide 20s linear infinite",
      },
    },
  },
};

주의점

이미 이미지에서는 일부러 데이터 간의 여백을 표시해두었다.

만약, 데이터 배열내에서 gap을 지정해주었다면 원본+복사본 또는 합쳐진 배열이 한 바퀴가 돌고난 뒤 연결되는 경우에 동일한 여백을 추가해주어야한다.

그렇지 않을 경우, 아래와 같이 데이터 간에 여백이 존재하지 않는 상태로 슬라이드가 연결된다.

각각의 데이터에 스타일링을 적용하거나 배열에서 padding-right를 적용하는 방법이 있다.

아래 코드는 gappadding-right를 적용한 하나의 배열, 하나의 애니메이션을 적용한 무한 슬라이드 컴포넌트이다.

export default async function PhotoSlide() {
  const { data } = await fetchPixelsPhotos();
  const photos = data.photos;
  return (
    <main className="w-screen h-screen flex justify-center items-center">
      <div className="flex w-fit overflow-hidden">
        <div className="flex pr-6 gap-6 animate-infiniteSlide">
          {[...photos, ...photos].map((photo) => (
            <Card
              key={photo.id}
              description={<p>photo.photographer</p>}
              imageSrc={photo.src.large2x}
              title={photo.photographer}
            />
          ))}
        </div>
      </div>
    </main>
  );
}

마우스 hover시, 무한 슬라이드 일시 정지

무한히 흘러가는 슬라이드에 사용자가 상호작용을 하거나 데이터를 확인하기 위하여 마우스 hover할 경우에는 멈추는 효과를 추가한다.

tailwind-CSS를 사용하여 스타일링을 적용하며, 이는 다른 스타일링을 사용하거나 순수 CSS을 사용하여도 다른방식을 사용해 동일하게 적용할 수 있다.

1. 슬라이드를 hover 할 경우 해당되는 모든 슬라이드가 일시 정지하게끔 group을 지정한다. (tailwind-CSS group 클래스)

// 두 개의 배열, 두 개의 애니메이션

export default async function PhotoSlide() {
  const { data } = await fetchPixelsPhotos();
  return (
    <main className="w-screen h-screen flex justify-center items-center">
      <div className="flex w-fit overflow-hidden group">
        <div className="flex pr-6 gap-6 animate-infiniteSlide group-hover">
          {data.photos.map((photo) => (
            <Card
              key={photo.id}
              description={<p>photo.photographer</p>}
              imageSrc={photo.src.large2x}
              title={photo.photographer}
            />
          ))}
        </div>
        <div className="flex pr-6 gap-6 animate-afterInfiniteSlide group-hover">
          {data.photos.map((photo) => (
            <Card
              key={photo.id}
              description={<p>photo.photographer</p>}
              imageSrc={photo.src.large2x}
              title={photo.photographer}
            />
          ))}
        </div>
      </div>
    </main>
  );
}
// 하나의 배열, 하나의 애니메이션

export default async function PhotoSlide() {
  const { data } = await fetchPixelsPhotos();
  const photos = data.photos;
  return (
    <main className="w-screen h-screen flex justify-center items-center">
      <div className="flex w-fit overflow-hidden group">
        <div className="flex pr-6 gap-6 animate-infiniteSlide group-hover">
          {[...photos, ...photos].map((photo) => (
            <Card
              key={photo.id}
              description={<p>photo.photographer</p>}
              imageSrc={photo.src.large2x}
              title={photo.photographer}
            />
          ))}
        </div>
      </div>
    </main>
  );
}

2. 애니메이션 일시 정지 애니메이션 추가 및 적용

tailwind-CSS에서는 애니메이션 상태를 일시 정지 시키는 스타일이 존재하지 않기 대문에 CSS를 직접 추가한다.

.group:hover .animate-infiniteSlide {
  animation-play-state: paused;
}

.group:hover .animate-afterInfiniteSlide {
  animation-play-state: paused;
}

이미지 상에는 마우스 커서가 그렇게 잘 보이진 않는데, 눈 크게 뜨고보면 마우스를 hover 할 때 슬라이드를 일시 정지 시킨다.

완성된 자동 재생 무한 슬라이드

마무리

주의점을 굳이 쓴 이유는 단지, 내가 구현시에 맞딱드린 어려웠던 부분이기 때문이다.

당시에 저부분이 해결되지 않았던 이유는 아주 기본적인 CSS의 이유였다. 박스모델의 width에는 margin이 포함되지 않기때문에 gap을 적용한다음 padding-right를 적용해야하는 것을 잊고 margin-right를 추가하고 있었기 때문이다.

또한, 무한 슬라이드를 구현하기위해 구글링을 많이 해봤더니 무한 슬라이드 일시정지 들을 다양하게 구현하는 것을 확인 할 수 있었다.

순수 CSS로 구현하는 방법이 있는 한편, 자바스크립트로 구현하는 방법도 있었다. 하지만, 자바스크립트로 해당 인터렉션을 관리하게되면 리플로우와 리페인팅이 계속적을 발생된다는 문제점이 발생한다.

순수 CSS로 구현할 수 있는 애니메이션 스타일링이었기 때문에 온전히 GPU에 맡기고 CSS로 무한슬라이드를 구현하는 방법을 선택했다.

  • 관련된 코드는 여기서 확인할 수 있다.(Next.js app router 사용) : 레포지토리

레퍼런스

profile
성실하게

1개의 댓글

comment-user-thumbnail
2024년 5월 15일

오와 대박! animation-play-state라는게 있는거는 처음 알았네요.
유익한 글 잘 보고 갑니다~~ 사진 덕분에 이해가 잘 되네용

답글 달기