CSS 슬라이더 (Hover 멈춤)

동물애호가·2024년 1월 14일
1

기능

목록 보기
1/1
post-thumbnail

🧐상황

한방향으로 연속적으로 움직이는 컨테이너들을 만들고, 마우스를 올렸을때 멈추는 애니메이션을 구현해야했다.

animation을 활용한 정석 방법 (추천)

⭐작동 방식

보여주고 싶은 카드들의 배열을 만들고, 이를 복제하여 나타낸 뒤 번갈아 가며 보여주는 방식

✔ 내가 만들고자 하는 컴포넌트들의 컨테이너를 배너라고하자.


[원본 배너]

원본배너복제배너를 한 방향으로 이동시켜주는 애니메이션을 실행시킨다

원본배너가 배너 너비만큼 이동하면, 복제배너의 뒤로 이동시킨다.

✔ 원본배너는 다시 배너 너비만큼 이동한다.

[복제 배너]

✔ 복제배너는 일정 방향으로 배너크기의 2배만큼 이동시켜주면 된다.

⭐ 사용자는 마치 하나의 배너가 무한으로 렌더링되는 것처럼 보임.

⭐ 사실은 2개의 배너가 번갈아가면서 나오는 것


그림으로 이해하기

노란색: 화면영역(UI 영역)

보라색: 원본배너

초록색: 복제배너

① 초기 상태

✏️ 원본기준 keyframes: 0%

화면영역보다 긴 원본배너를 위치시키고, 뒤에 복제배너를 위치시킨다.

② 배너의 너비만큼 이동 (원본 + 복제배너)

✏️ 원본기준 keyframes: 0% to 50%

보라색 원본배너가 본인의 길이만큼 애니메이션을 작동시켜 이동한다.

이때, 당연히 복제배너는 뒤를 따라간다.

cf) 해당 상태를 보는 유저는 12345 1234 까지 보았다.

③ 원본을 복제 뒤로 이동

✏️ 원본기준 keyframes: 50% to 50.1%

보라색 원본배너를 복제배너 뒤에 위치시킨다 (keyframes: 50% → keyframes: 50.01%, 찰나의 순간)

이후, 계속 이동방향으로 이동시키면 유저는 연속된 숫자를 보게 됨

④ 배너 너비만큼 이동

✏️ 원본기준 keyframes: 50.1% to 100%

배너 너비만큼 이동

복제배너는 처음 자리로 이동시키고(animation 속성) 1번부터 다시 실행 (infinite animation)


저는 React, tailwind로 구현하였습니다.

필요한 애니메이션은 tailwind.config.js 에 정의해주었습니다.

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      keyframes: {
        rollingleft1: {
          "0%": { transform: "translateX(0)" },
          "50%": { transform: "translateX(-100%)" },
          "50.1%": { transform: "translateX(100%)" },
          "100%": { transform: "translateX(0)" },
        },
        rollingleft2: {
          "0%": { transform: "translateX(0)" },
          "100%": { transform: "translateX(-200%)" },
        },
      },
      animation: {
        rollingleft1: "rollingleft1 33s linear infinite",
        rollingleft2: "rollingleft2 33s linear infinite",
      },
    },
  },
  plugins: [],
};

🖥️ 코드 (React, tailwind)

import React, { useState } from "react";
// 예시 배열
const arr = [
  "image1",
  "image2",
  "image3",
  "image4",
  "image5",
  "image6",
  "image7",
];
const Funct1 = () => {
  const [isHovered, setIsHovered] = useState(false);
  const display = arr.map((text, index) => (
    <li key={index} className="mx-2">
      {text}
    </li>
  ));
  return (
    <div className="relative flex w-1/2  overflow-hidden border-4 border-solid border-black">
      {/* 원본 */}
      <ul
        className={`animate-rollingleft1 flex h-full w-[110%] ${isHovered && "[animation-play-state:paused]"}`}
        onMouseOver={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}

        {display}
      </ul>
      {/* 카피 */}
      <ul
        className={`animate-rollingleft2 flex h-full w-[110%] ${isHovered && "[animation-play-state:paused]"}`}
        onMouseOver={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}

        {display}
      </ul>
    </div>
  );
};
export default Funct1;

❓직면한 문제

animation-play:paused 를 통해 애니메이션을 멈출 수 있다는 것을 알게되었지만, 원본과 복사본이 hover시 동시에 멈추어야 했다.

✏️ isHovered state를 만들어서 동적으로 css를 부여해주었다

react-fast-marquee (lib, 비추천)

공식문서
React Fast Marquee

→ 작동방식은 매우 간단해서, 단순한 구현의 경우 쉽게 제작 가능

But, 내 사이트에 원하는 딱 맞춤 방식은 불가능

ex) 크기를 마음대로 맞추지 못함

해당 방식으로 코딩하고, 결과를 보니 다음과 같이 설계되어 있었음

문제는, children 바로 위에 있는 div의 height를 따로 설정해주지 않았다는 것. → 내가 원하는 크기로 고정시켜둬야 함…?

😡 width, height에서 부모 크기의 %를 사용할 수 없게됨

→ 결국 해당 코드를 뜯어보면, 정석 방식을 만들기 쉽게 컴포넌트화해서 배포한 거나 다름없음.. 그냥 내가 만드는게 커스터마이징하기도 편할 것 같다 생각함

profile
서두르지 말되, 멈추지 말라 (Tistory로 이사중입니다!)

0개의 댓글