React 슬라이딩 캐러셀 만드는 법 (Slider, Carousel)


이전에 진행했던 ‘디스트리’ 프로젝트에서 끊기지 않는 무한 슬라이딩 캐러셀을 만들어보려다 실패한 경험이 있다. 당시에는 react는 물론이고 javascript도 잘 다루지 못했던 때라 이번 기회를 빌어 무한 슬라이딩 캐러셀 제작법을 정리해보려 한다.

sliding

메인 컨셉

가장 중요한 구현 컨셉은, 양쪽에 fake 배너를 만들어줘서 눈속임을 줘야 한다는 것이다.
물론 실제로 모든 배너들이 3D로 연결되어 무한 슬라이딩을 구현하면 좋겠지만 쉽지 않은 것이 현실이다.

따라서 아래와 같이 가짜 배너를 양 끝에 배치시켜야 한다. 이해가 아직은 쉽지 않을 수 있는데 두 번째 그림을 보면 쉽게 이해될 수도 있다.

가짜를 배치한다

쉽게 설명하면 transition의 특성을 이용한 것이다.
우리는 각 슬라이드들이 넘어가는 것을 어떻게 인지할까?
첫 째,그림이 바뀌는 것을 인지하는 것, 그리고 둘 째, 같은 그림이더라도 넘어가는 과정의 transition을 통해 배너가 슬라이딩 되는 것을 인지한다.

하지만 아래와 같이 가짜 배너 1번이 화면 중앙에 위치해 있을 때 transition을 제거해주고 원래 진짜 1번의 위치로 이동시키면 트랜지션 효과도 없을 뿐더러 같은 그림에서 같은 그림으로 변경되기에 우리는 배너의 변화를 눈으로 알아챌 수 없다.

따라서 아래 그림에서와 같이, 진짜 4번 → 가짜 1번으로의 이동이 끝난 시점(transitionend)에 transition을 제거한 뒤 -> 진짜 1번의 위치로 이동하게 되면, 사람의 눈으로 봤을 때 그대로 1번에 고정되어있는 것과 같은 착각을 일으킨다. 따라서 이 방법을 사용하면 슬라이드가 끊기지 않고 계속 연결되며 슬라이딩 되는 것과 같은 효과를 줄 수 있는 것이다.

눈속임의 방법

각 배너들이 담길 Carousel을 만든다

1
2
3
4
5
6
7
8
// 기본 구성은 이하와 같다. main이라는 큰 틀 안에 carousel을 만들어주고, 그 안에 각자 이미지 배너들을 넣어주면 된다.
<div className='main'>
<ul className='carousel'>
<li className='banner'>
<img src='#' alt='' />
</li>
</ul>
</div>

위치 조정

정렬되지 않은 상태

위에서 미리 짜둔 구조에 맞춰 이미지를 넣고 overflow:hidden 을 해주면 이와 같은 모양새가 나온다.
하지만 이는 정렬이 되지 않은 날 것의 상태이기 때문에 transform:translateX() 를 통해 위치를 조정해줘야 한다.

이런 초기값에서

비정렬 예시

우리가 바라는 디폴트 값은 이와 같은 형태로 정렬된 상태여야 한다.

정렬 예시

이를 위해선 transform:translateX()를 통해 얼만큼 움직여야 원하는 배너가 중앙에 위치할 수 있는지 계산해줘야 한다.

거리 계산 방법 (지극히 주관적)

우선, 좌측 끝에서 중앙에 위치시키고 싶은 배너의 좌측 끝부분 까지의 거리를 구한다.

값1

그리고 해당 배너가 중앙에 위치하게 된다면 남게 될 좌측 공백의 거리를 계산한다.

값2

이제 두 값을 빼면 transform을 통해 초기에 움직여야 하는 거리가 계산된다.

종합적인 그림 예시로 다시 요약을 하자면…

계산법 요약

왼쪽끝 ~ 중앙에 위치하길 바라는 배너의 좌측 끝 (BannerWidth + margin)에서 해당 배너가 중앙에 위치했을 때 남는 좌측 공백값을 빼주면 얼만큼 이동해야 배너가 중앙에 위치하는지 계산할 수 있다.


그럼 이제 슬라이딩을 어떻게 구현하면 될까?

우선 슬라이드를 움직일 버튼 두 개를 만들고, 각 버튼의 onClick 이벤트에 함수를 걸어준다.

1
2
3
4
5
6
7
// mdArrow는 방향표시 svg react icons이므로 크게 신경 쓸 필요 없다.
<button className="leftArrow" onClick={moveLeft}>
<MdArrowBackIosNew />
</button>
<button className="rightArrow" onClick={moveRight}>
<MdArrowForwardIos />
</button>

moveLeft와 moveRight함수의 코드 로직

우선, 현재 가운데에 위치해 있는 배너가 몇 번째 배너인지 체크하기 위한 currentIndex state가 필요하다.

그리고 필자는 추가적으로 얼만큼 움직여야 하는지를 css에 전달해 줄 distance, 그리고 transition값을 css에 전달해 줄 timing이라는 state도 추가해 활용했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const moveLeft = () => {
// 일반적인 transtion 슬라이딩일 경우에는 그냥 index값을 감소시켜주고, 트랜지션 값도 0.3초를 준다.
// 그리고 계속 배너의 넓이 + margin값 만큼 좌측으로 이동시켜준다.(기존 거리 - 더 이동해야 하는 거리)
if (currentIndex > 2) {
setCurrentIndex(currentIndex - 1);
setTiming(0.3);
setDistance(distance - 64.5);
return;
}
};

const moveRight = () => {
if (currentIndex < 12) {
setCurrentIndex(currentIndex + 1);
setTiming(0.3);
setDistance(distance + 64.5);
return;
}
};
1
2
3
4
5
6
7
8
9
10
// SCSS에서 state를 이하와 같이 전달받아 활용했다.
// useRef를 쓸까도 고민했지만, 일단 state로 관리하는게 더 편할 것 같아서 이와 같이 구현했다.
const Slider = styled.div`
position: relative;
display: flex;
width: 100%;
height: 100%;
transform: ${(props) => `translateX(-${props.distance}vw)`};
transition: ${(props) => `transform ${props.timing}s ease`};
`;

슬라이드가 끝에 닿았을 때의 코드 로직 (중요)

이제 transition 값을 none으로 지정해준 뒤, 가짜 배너에서 진짜 배너로 이동시켜주는 눈속임을 행해야 할 때다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 슬라이더 안에 props로써 distance, timing 들을 넣어준다.
// 그리고 onTransitionEnd 이벤트를 리스닝 하도록 한다.
<div
ref={slideCarousel}
distance={distance}
timing={timing}
onTransitionEnd={handleFlip}
>
<Banner>4</Banner> // 가짜
<Banner>5</Banner> // 가짜
<Banner>1</Banner>
<Banner>2</Banner>
<Banner>3</Banner>
<Banner>4</Banner>
<Banner>5</Banner>
<Banner>1</Banner> // 가짜
<Banner>2</Banner> // 가짜
</div>

이제 onTransitionEnd에 의해 실행되는 함수 handleFlip 코드를 작성해주면 된다.

예를 들어 가짜 9번으로 이동이 끝났을 때 (좌측이동) 진짜 9번으로 트랜지션 없이 이동하면 된다.

눈속임을 해야할 때

진짜 1번 → 가짜9번으로의 이동이 완전히 끝났을 때 트랜지션을 제거하고 눈속임을 줘야하기 때문에 onTransitionEnd에 함수를 걸어준 것이다.

진짜 배너로 눈속임 이동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const handleFlip = () => {
if (currentIndex <= 2) {
// 진짜 9번의 인덱스인 11번으로 인덱스값 세팅
setCurrentIndex(11);
// 트랜지션 0으로 지정
setTiming(0);
// 진짜 9번이 화면 가운데에 위치했을 경우의 거리값
setDistance(626.5);
}
// 이쪽은 우측 이동했을 경우의 코드
if (currentIndex >= 12) {
setCurrentIndex(3);
setTiming(0);
setDistance(110.5);
}
};

React 슬라이딩 캐러셀 만드는 법 (Slider, Carousel)

https://hoonjoo-park.github.io/react/Sliding_Carousel/

Author

Hoonjoo

Posted on

2022-01-20

Updated on

2022-02-07

Licensed under

Comments