한방향으로 연속적으로 움직이는 컨테이너들을 만들고, 마우스를 올렸을때 멈추는 애니메이션을 구현해야했다.
보여주고 싶은 카드들의 배열을 만들고, 이를 복제하여 나타낸 뒤 번갈아 가며 보여주는 방식
✔ 내가 만들고자 하는 컴포넌트들의 컨테이너를 배너라고하자.
[원본 배너]
✔ 원본배너와 복제배너를 한 방향으로 이동시켜주는 애니메이션을 실행시킨다
✔ 원본배너가 배너 너비만큼 이동하면, 복제배너의 뒤로 이동시킨다.
✔ 원본배너는 다시 배너 너비만큼 이동한다.
[복제 배너]
✔ 복제배너는 일정 방향으로 배너크기의 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: [], };
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
→ 작동방식은 매우 간단해서, 단순한 구현의 경우 쉽게 제작 가능
But, 내 사이트에 원하는 딱 맞춤 방식은 불가능
ex) 크기를 마음대로 맞추지 못함
해당 방식으로 코딩하고, 결과를 보니 다음과 같이 설계되어 있었음
문제는, children 바로 위에 있는 div의 height를 따로 설정해주지 않았다는 것. → 내가 원하는 크기로 고정시켜둬야 함…?
😡 width, height에서 부모 크기의 %를 사용할 수 없게됨
→ 결국 해당 코드를 뜯어보면, 정석 방식을 만들기 쉽게 컴포넌트화해서 배포한 거나 다름없음.. 그냥 내가 만드는게 커스터마이징하기도 편할 것 같다 생각함