구현해보고자 하는 Carousel 컴포넌트의 특징은 다음과 같다.
1. 사용자의 화면 width에 따라, 3개 혹은 4개의 카드가 보여진다.
2. 3초 마다 카드가 오른쪽으로 이동하고 마지막에 다시 원 상태로 돌아온다.
3. 사용자는 화살표 버튼을 클릭해서 카드를 한 개씩 넘길 수 있다.
4. 사용자의 화면 width를 원활하게 추적하기 위해, custom hook을 활용할 것이다.
사용자의 화면을 원활하게 추적하는 custom hook인 useWindowWidth를 활용하고자 한다.
/utils/useWindowWidth.ts
import { useState, useEffect } from 'react';
function useWindowWidth() {
const isClient = typeof window === 'object';
const [windowWidth, setWindowWidth] = useState(isClient ? window.innerWidth : 0);
useEffect(() => {
if (!isClient) return; // SSR일 경우 리턴합니다.
function handleResize() {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
handleResize();
return () => window.removeEventListener('resize', handleResize);
}, [isClient]); // isClient의 변경에 따라 훅을 실행합니다.
return windowWidth;
}
export default useWindowWidth;
Next.js 프로젝트이므로, SSR 상황에서 오류가 발생하지 않도록 조건문을 작성했다.
의존성 관리를 위해, handleResize 함수를 useEffect 내부에 작성했다.
메모리 누수를 방지하기 위해, 컴포넌트가 unmount 될 때, 이벤트 핸들러를 제거했다.
구현한 Carousel 컴포넌트는 다음과 같다.
import Link from 'next/link';
import Image from 'next/image';
import { useState, useEffect } from 'react';
import left from 'assets/icons/main/left.svg';
import right from 'assets/icons/main/right.svg';
import AdCard from './AdCard';
import WeatherCards from './WeatherCards';
import { CarouselData } from 'interfaces/main';
import useWindowWidth from 'utils/useWindowWidth';
interface Props {
carouselData: CarouselData[];
}
const Carousels = ({ carouselData }: Props) => {
const width = useWindowWidth();
const [index, setIndex] = useState(0);
useEffect(() => {
let timeout: NodeJS.Timeout;
timeout = setTimeout(
() =>
index * 4 > carouselData?.length
? setIndex(0)
: setIndex((prev) => prev + 1),
3000
);
return () => clearTimeout(timeout);
}, [index, carouselData.length]);
return (
<div className="flex items-center gap-5 justify-center min-w-[1024px] max-w-1320 mx-auto">
<Image
src={left}
alt=""
className={`w-10 cursor-pointer
${index === 0 && 'opacity-0'} `}
onClick={() => index > 0 && setIndex((prev) => prev - 1)}
/>
<div className="overflow-hidden">
<div
className={`flex gap-6 ease-in-out duration-1000 `}
style={{ transform: `translateX(calc(${index}*-612px))` }}
>
{carouselData?.map((asset: CarouselData, i: number) => (
<div key={i}>
{(width > 1024 ? (i + 1) % 4 === 0 : (i + 1) % 3 === 0) ? (
<AdCard />
) : (
<Link href={`/detail/${asset['ITEM_CD_DL']}`}>
<WeatherCards
Exp_CVaR={asset['EXP_CVaRNTS']}
exchg={asset['HR_ITEM_NM']}
loc={asset['LOC']}
krName={asset['ITEM_KR_NM']}
....
/>
</Link>
)}
</div>
))}
</div>
</div>
<Image
src={right}
alt=""
className={`w-10 cursor-pointer
${index * 4 > carouselData?.length && 'opacity-0'} `}
onClick={() =>
index * 4 < carouselData?.length && setIndex((prev) => prev + 1)
}
/>
</div>
);
};
export default Carousels;
사용자 화면의 width를 추적하기 위해 custom hook을 불러왔다.
transform
속성을 활용하기 위해, inline css스타일인 style을 활용했다.
(tailwind css를 활용하므로, 고 수준의 css속성을 다루는데 한계가 있기 때문)
translateX
로 인해, index의 변화에 따라 612px만큼 X축 방향으로 이동하게 된다.
index * 4 < carouselData?.length
를 통해, 끝에 다달았을 때, 무엇을 할 지 결정한다.
(ex. index * 4 > carouselData?.length ? setIndex(0) : setIndex((prev) => prev + 1)
이 경우 카드가 끝에 다다랐을 때, index가 다시 0이 되어 처음으로 돌아오고 이외에는 index값을 더하며 카드를 오른쪽으로 이동시킨다.
setTimeout
을 활용하여 3초마다 카드가 오른쪽으로 이동하도록 설정했다.
이번 포스팅에서 Web View에서의 Carousel 컴포넌트를 구현해보았다. Carousel 컴포넌트는 사용자에게 보여주고 싶은 정보들을 보기 편한 UI로 가독성있게 전달하고자 할 때 자주 활용되는 컴포넌트이다.
다음 포스팅에는 Mobile View에서의 Carousel 컴포넌트를 구현해보고자 한다.
좋은글감사합니다!!!!