메인 페이지에 존재하는 Funding List 만들기

다음과 같은 디자인을 비슷하게 만들어 갈 예정이다.
기능 확인하기
- 서버로 부터 카드 정보를 받는다.(image, title, maxCount, curCount)
- 받은 정보를 가지고, slide를 만든다.
- slide의 기능은 prev,next 버튼 클릭시 카드가 움직일 것이고, 방향은 왼쪽, 오른쪽으로 가능하다.
구현 하기
Button 구현하기 (next, prev)
처음에 가장 만들기 쉬운것부터 접근했다. button을 클릭했을 때, 카드가 이전으로 가거나 다음으로 넘어가도록 구현하기로 했다. 그렇게 하기 위해서는 현재 상태를 관리해주는 state가 필요했고, useState를 이용해서 관리하도록 했다.
const [currentIndex, setCurrentIndex] = useState(0);
const prevSlide = () => {
setCurrentIndex(currentIndex === 0 ? data.length - 1 : currentIndex - 1);
};
const nextSlide = () => {
setCurrentIndex(currentIndex === data.length - 1 ? 0 : currentIndex + 1);
};
prev를 계속누르다가 현재 값이 ‘0’이 되었을 때에 대한 interlock으로 조건을 넣어 마지막으로 넘어가도록 설정해놨고, next의 경우는 마지막에 도착했을때, 처음으로 돌아가도록 설정했다.
Card List 구현하기
이제 card List를 구현할 차례이다. props로 data[] 형태를 전달 받을 예정이고, 해당 데이터는 다음과 같다.
type data = {
title: string;
maxAmount: number;
curAmount: number;
image?: string | StaticImageData;
};
interface SlideProps {
data: data[];
}
다음과 같은 내용을 props로 전달받은 다음, map 함수를 이용해서 FundingCard 컴포넌트를 만들 예정이다.
{data &&
data.map((value, index) => (
<div key={index} className={styles.carouselItem}>
<FundingCard
title={value.title}
maxAmount={value.maxAmount}
curAmount={value.curAmount}
image={value.image}
/>
</div>
))}
움직임 표현하기
나는 개인적으로 이걸 표현하기가 너무 어려웠다. 어떻게 표현할 수 있을까 고민을 많이했고, 차근차근 계획을 세웠다.
- currentIndex에 따라서 transform 값을 넣어야 된다.
- 방향은 x 방향으로 움직일 예정이기 때문에, translateX를 이용해서 변화값을 준다.
- 카드들간은 약간의겹침이 포함되어 있고, 높이차이를 zIndex를 이용해 표현해야한다.
currentIndex에 따라서 transform 값을 넣어야 된다.
현재나는 vanilla-extract를 사용하고 있어서, props를 통해서 현재 currentIndex 값을 전달할 수 가 없었다.
그래서 내가 생각한 방법은 useRef를 사용해서 해당 태그의 참조값을 가져와서 style을 직접 입력하는 방법이다.
useRef를 이용해서 부모요소의 참조값을 가져와서 slide.styles.transform을 이용해서 다음과 같이 작업을 했다.
const slideWrapper = useRef<HTMLDivElement>(null);
useEffect(() => {
if (slideWrapper.current) {
const slideItems = Array.from(
slideWrapper.current.children
) as HTMLElement[];
slideItems.forEach((slide, index) => {
slide.style.transform = `translateX(${(index - currentIndex) * 30}%)`;
});
}
}, [currentIndex]);
...
return (
...
<div className={styles.slideContainer} ref={slideWrapper}>
{data &&
data.map((value, index) => (
<div key={index} className={styles.carouselItem}>
<FundingCard
title={value.title}
maxAmount={value.maxAmount}
curAmount={value.curAmount}
image={value.image}
/>
</div>
))}
</div>
부모 요소 태그인 div 태그에 ref를 입력하고, 이 자식요소값들을 가져왔다.
Array.from() 메서드를 이용해서 요소값들을 배열로 만들었고, 각 자식요소들에 각각 접근하기 위해서
forEach를 이용해서 각각의 자식요소에 접근했다.
자, 이제 각각의 자식요소에 접근을 할 수 있게 되었고 어떻게 translateX값을 줘야 내가 원하는 기능을 표현할 수 있을까 생각을 했다.
방향은 x 방향으로 움직일 예정이기 때문에, translateX를 이용해서 변화값을 준다.
currentIndex은 다음과 같이 동작을 한다. 만약 data 값이 8개라고 한다면,
0,1,2,3,4,5,6,70,1,2,3,4,5,6,7,0,…..
다음과 같이 동작할 것이고, 이거에 맞춰서 translateX 값을 줘야한다.
translateX값은 초기 값을 기준으로 크기를 크게 증가시키면서 움직이는 효과를 준ㄷ.
예를 들어 1번째 카드기준으로 얘기를 하면, 처음에는 currentIndex가 0 이기 때문에 translateX가 0이여서 움직임이 없을 것이다. 그다음 만약 next로 1칸 움직인다고 하면, 첫번째 카드는 왼쪽으로 움직어야 하고 2번째 카드도 왼쪽으로 움직어야한다. 근데 첫번째 카드는 처음에 tranlateX 값이 ‘0’ 이었으므로, 왼쪽으로 가기 위해서는 ‘-’ 값으로 이동을 해야되고, 결론적으로 ‘0’ —> ‘-30%’ 로 이동을 할것이다. 2번째 카드는 ‘30%’ →’0’으로 이동을 할것이다. 이렇게 되면, 카드는 왼쪽으로 이동한것처럼 보인다.
그래서 계속 next 버튼을 누르게 된다면, 첫번째 카드는 다음과 같이 tranlateX가 작동할 것이다.
0 , -30%, -60%, -90%, -120%, -150%, -180%, -210% ..
처음 기준으로 더 많은 폭을 이동하면서 카드가 한개씩 움직이는 것 처럼 보이도록 하는것이다.
slideItems.forEach((slide, index) => {
slide.style.transform = `translateX(${(index - currentIndex) * 30}%)`;
});
30%로 정한것은 카드가 얼마나 겹쳐보일것인가를 정한것이고 이거는 디자인이나 원하는 기준에 따라 바꿔주면 된다.
카드들간은 약간의겹침이 포함되어 있고, 높이차이를 zIndex를 이용해 표현해야한다.
이제 마지막 요소인 zIndex이다. 이것도 처음에 어떻게 구현을 해야될까 고민을 많이했다. 이게 진짜 고민이 된게 zIndex는 다음과 같이 적용이 되야했다.
나는 카드 5장을 기준으로 UI를 보여주려 했다. 가운대 카드가 가장 앞에 표시가 되고, 그 다음 2개의 카드가 양쪽에 있고, 그 아래에 2개의 카드가 또 양쪽에 있게 약간 피라미드 형태로 표현을 하고 싶었다.
그렇다면, currentIndex를 기준으로 계속 가운대값을 기준으로 zIndex를 대칭적으로 입력해야 했고, 어떻게 표현할 수 있을까 고민을 하다가 다음과 같이 구현했다.
slideItems.forEach((slide, index) => {
const distanceFromCenter = Math.abs(index - currentIndex);
slide.style.zIndex =
distanceFromCenter <= 2 ? `${5 - distanceFromCenter}` : "0";
slide.style.transform = `translateX(${(index - currentIndex) * 30}%)`;
});
이번에도 첫번째 카드를 기준으로 생각을 해봤다. 만약에 첫번째 카드가 가장 앞으로 나오게 하고 싶으면 어떻게 해야될까? 카드는 왼쪽에서 오른쪽으로 표현하도록 translateX를 이용해 구현했다. 그러면 첫번째 카드 왼쪽에는 아무 카드도 없고, 첫번째 카드가 가운대로 오게 하려면 오른쪽으로 2장의 카드만 있으면 된다고 생각했다.
그리고 2번째카드가 가운대로 오게 하기 위해서는 왼쪽에는 1번째 카드 1개 오른쪽에는 2장이 있을것이다. 3번째 카드는 왼쪽 2장 오른쪽 2장…
4번째 카드, 5번째 카드 나머지들도 다 동일하게 적용될것이다.
가운대는 어떻게 알까? forEach로 각 자식요소들의 index를 알 수 있다. 그렇다면 index에서 currentIndex를 뺀 값이 0이면 그게 가운대가 되지 않을까 라고 생각을 했다.
그리고 가운대를 기준으로 양쪽 2장이 대칭이 되는 index값을 가져야 된다. 그렇다면 Math.abs를 이용해서 절대값으로 표현을 하고, 0을 기준으로 양쪽 2장의 카드의 zIndex를 대칭을 주기 위해서 양쪽 2장에 해당되는 카드 distanceFromCenter <= 2 들은 ${5 - distanceFromCenter}같이 zIndex 값을 주었고, 나머지들은 어짜피 화면내에 보일일이 없기 때문에 zIndex 값을 '0'을 주었다.
다시 얘기를 하면 distanceFromCenter 값은 currentIndex가 증가함에 따라 다음과 같이 표현된다.
0,1,2,3,4,5,6,7
1,0,1,2,3,4,5,6
2,1,0,1,2,3,4,5
3,2,1,0,1,2,3,4
4,3,2,1,0,1,2,3
5,4,3,2,1,0,1,2
6,5,4,3,2,1,0,1
7,6,5,4,3,2,1,0
0값을 기준으로 양쪽 2개의 값 즉, 2보다 같거나 작은 값들은 zIndex 값을 부여하고, 나머지는 0으로 처리했다.
전체 코드
import { useCallback, useEffect, useRef, useState } from "react";
import Image, { StaticImageData } from "next/image";
import * as styles from "./Slide.css";
import { FundingCard, ArrowLeft, ArrowRight } from "@components";
type data = {
title: string;
maxAmount: number;
curAmount: number;
image?: string | StaticImageData;
};
interface SlideProps {
data: data[];
}
const Slide = ({ data }: SlideProps) => {
const slideWrapper = useRef<HTMLDivElement>(null);
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
if (slideWrapper.current) {
const slideItems = Array.from(
slideWrapper.current.children
) as HTMLElement[];
slideItems.forEach((slide, index) => {
const distanceFromCenter = Math.abs(index - currentIndex);
slide.style.zIndex =
distanceFromCenter <= 2 ? `${5 - distanceFromCenter}` : "0";
slide.style.transform = `translateX(${(index - currentIndex) * 30}%)`;
});
}
}, [currentIndex]);
const prevSlide = () => {
setCurrentIndex(currentIndex === 0 ? data.length - 1 : currentIndex - 1);
};
const nextSlide = () => {
setCurrentIndex(currentIndex === data.length - 1 ? 0 : currentIndex + 1);
};
return (
<div className={styles.wrapper}>
<div className={styles.container}>
<ArrowLeft onClick={prevSlide} />
<div className={styles.slideWapper}>
<div className={styles.slideContainer} ref={slideWrapper}>
{data &&
data.map((value, index) => (
<div key={index} className={styles.slidelItem}>
<FundingCard
title={value.title}
maxAmount={value.maxAmount}
curAmount={value.curAmount}
image={value.image}
/>
</div>
))}
</div>
</div>
<ArrowRight onClick={nextSlide} />
</div>
</div>
);
};
export default Slide;
시연영상
부족한점
아직까지 구현하지 못한부분은 원형으로 카드가 돌아가게 하는기능이다. 일단 작업에 일정이 있기 때문에 현재는 이부분 까지 구현을 했고, 나머지 부분에 대해 개선하기 위해서는 css top 속성을 이용해서 위아래 편차를 줘야 하고, 처음과 끝부분에 대해 카드가 원형으로 돌아갈 수 있도록 구현해야될거같다.
이부분은 프로젝트가 어느정도 마무리된 이후에 추가작업할 예정이다.
'prev > NextJS' 카테고리의 다른 글
| Nextjs 14, tanStack/react-query를 이용한 SSR (0) | 2023.12.18 |
|---|---|
| GiftWave 프로젝트/휴대폰인증 (setInterval, useInterval) (0) | 2023.07.22 |
| Molecule 과 Organism (by PhoneAuth) (0) | 2023.07.19 |
| Nextjs 카카오 소셜 로그인(REST API 방식) (0) | 2023.07.08 |
| Environment Variables (.env 공식문서 번역하기) (0) | 2023.07.03 |