일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 다이나믹프로그래밍
- 코딩테스트
- 백준구현
- 프로그래머스JS
- 리액트
- 리액트댓글기능
- 백준골드
- 프로그래머스코테
- 백준js
- css기초
- 알고리즘
- JS
- 리액트커뮤니티
- CSS
- 포이마웹
- 안드로이드 스튜디오
- 백준
- 백준nodejs
- js코테
- dp알고리즘
- 코테
- 익스프레스
- 백준알고리즘
- 자바스크립트
- 프로그래머스
- 몽고DB
- HTML5
- HTML
- JS프로그래머스
- 백준구현문제
- Today
- Total
개발새발 로그
Carousel을 구현하면서 본문
📖들어가며..
이전에 이미지 슬라이드를 구현했는데
이번에는 Carousel을 바로 구현해보려고 한다.
이미지 슬라이드와 Carousel은 조금의 차이점이 존재한다.
이미지 슬라이드(Slide)와 캐러셀(Carousel)은 웹 디자인과 UI/UX에서 사용자가 콘텐츠를 탐색할 수 있도록 돕는 시각적 도구입니다. 둘 다 비슷한 기능을 제공하지만 사용성과 목적에 따라 차이가 있습니다.
이미지 슬라이드(Image Slide)
이미지 슬라이드는 정렬된 콘텐츠를 일방향으로 보여주는 방식입니다.
특징:
- 단순한 전환: 이미지를 순차적으로 하나씩 표시하며 보통 자동 재생 또는 수동으로 탐색 가능합니다.
- 일반적인 흐름: 슬라이드가 끝에 도달하면 보통 다시 첫 번째로 돌아가거나 멈춥니다.
- 선형적 구조: 이미지가 한 방향(왼쪽에서 오른쪽 또는 그 반대)으로만 이동합니다.
사용 사례:
- 프레젠테이션 형식 (예: PowerPoint 슬라이드)
- 간단한 이미지 갤러리
캐러셀(Carousel)
캐러셀은 슬라이드의 발전된 형태로, 동적인 콘텐츠 탐색이 가능하도록 설계된 UI 컴포넌트입니다.
특징:
- 다중 항목 표시: 한 번에 여러 이미지를 한 화면에 표시할 수 있습니다.
- 루프(Looping): 끝없이 반복되는 방식으로 콘텐츠가 순환됩니다.
- 상호작용 요소:
- 네비게이션 버튼(화살표)
- 드래그 또는 스와이프 기능
- 썸네일 또는 도트 네비게이션(현재 위치 표시)
- 다양한 콘텐츠 가능: 단순 이미지뿐 아니라 텍스트, 비디오, 버튼 등 다양한 요소를 포함할 수 있음.
사용 사례:
- 전자상거래 사이트의 추천 상품 섹션
- 웹사이트의 주요 배너
- 모바일 앱의 안내 페이지
이를 인지하고 Carousel을 구현해보려 한다.
1. Carousel 동작 설계
먼저 Carousel은 이미지 등이 회전목마처럼 순회가 되는 것이다.
그러면 이를 어떻게 보여줄지 설계를 해야한다.
간단한 방법은 두 가지가 있다.
1. 전체 리스트를 나열한 상태에 끝지점(처음)에서 오른쪽(좌측) 이동시 우측(좌측)에 리스트를 복제해서 붙여놓고 이동하는 방법
2.하나의 이미지만 보여주고 오른쪽(왼쪽) 이동시 그 위치에 해당되는 데이터를 보여주는 방법(애니메이션 이용)
나는 2번째 방식으로 했다.
간단하게 방식을 설명하면
현재 위치한 current는 중앙에 위치하고
왼쪽 혹은 오른쪽으로 슬라이드 할 때
해당되는 요소를 애니메이션을 통해 나오게 하는 것이다.(forward 애니메이션이다.)
그리고 AnimationEnd를 활용해 애니메이션이 끝날 때마다 current를 바꿔주는 것이다.
current 요소를 제외하면 모든 요소는 translateX(-200%)와 같은 속성으로 화면에 보이지않고 어딘가에 위치하도록 했다.
이제 이 애니메이션을 슬라이드할 때 붙여주면 된다.
styled-component로 가능은 할 테지만 그렇게 되면 각 슬라이드 아이템의 상태관리가 복잡해질 것 같았고,
class를 추가하는 방식으로 결정했다.
currentIndex라는 useState 상태 하나만 관리하고,
itemRef라는 ref로 각 슬라이드 아이템 DOM에 접근하기 위해 저장했다.
itemRef는 HTMLLIElement[] 타입으로 되어있는 것이다.
이제 오른쪽 혹은 왼쪽 슬라이드를 할 때 아래와 같은 계산을 통해 다음 인덱스(next)를 찾는다.
여기서 구한 nextIndex와 현재 currentIndex를 가지고
ref에 저장했던 슬라이드 요소에 접근한 뒤
해당되는 애니메이션을 적용하는 것이다.
이때 중요한 것은 해당 애니메이션이 끝났을 때
아래와 같이 현재 currentIndex를 바꿔줘야한다.
그리고 원래는 classList.remove와 같은 로직으로 애니메이션을 동작하게하는 className을 제거해줘야한다.
하지만 위 로직에서는 그런 로직이 존재하지않고있다.
그렇지만 실제로 동작할 때는 알아서 해당 애니메이션 className이 제거되고 있다.
React는 상태를 기반으로 DOM을 재생성하기 때문에 다음과 같은 일이 발생한다.
- setCurrentIndex(next)가 호출되면 React는 컴포넌트를 새로 렌더링
- 새로 렌더링된 DOM 요소는 React가 관리하는 상태(props, state)를 기준으로 다시 생성
- 수동으로 추가한 클래스(classList.add)는 React 상태에 포함되지 않기 때문에, 리렌더링된 DOM에는 반영되지 않는다.
즉, React는 수동으로 추가된 DOM 변경 사항을 "모르기" 때문에 새 DOM에서는 이런 변경 사항이 반영되지 않는 것입니다.
🤔왜 React가 수동 변경 사항을 무시하지?
React는 Declarative UI 방식으로 작동한다.
- React는 상태와 props를 기반으로 UI를 정의하고, React가 관리하지 않는 DOM 변경 사항은 다음 렌더링 시 무시
- 이는 React의 철학에 기반한 설계로, "상태 기반 UI 업데이트"를 보장하기 위해 일부러 수동 DOM 조작을 무시하는 것
이 방식의 장점은
- React의 Virtual DOM과 실제 DOM 간의 동기화를 쉽게 유지.
- 선언적 방식으로 UI를 관리하므로 코드가 명확하고 유지보수가 용이.
이런 이유로 따로 해당 로직을 작성하지 않아도 자동으로 수행하는 것이다.
그러면 이런 생각이 들 수 있다.
어차피 자동을 제거되는 거니까 안전하게 제거 로직을 추가해도 되지않을까?
하지만 아래와 같이 로직을 추가하면 애니메이션이 이상하게 실행되는 문제가 생긴다.
🤔React 상태 업데이트와 DOM 클래스 동기화 간의 타이밍 문제
setCurrentIndex(next)를 호출하면 React는 상태를 기반으로 Virtual DOM을 새로 생성하고, 이를 실제 DOM에 반영한다.
이 과정에서 다음과 같은 일이 발생할 수 있다.
- classList.remove로 클래스를 제거했지만, setCurrentIndex가 호출되면 React는 컴포넌트를 리렌더링
- React는 상태(currentIndex)를 업데이트하며, 새로운 DOM을 생성해 기존 DOM과 교체하거나 재조정합
- 이 과정에서 classList.add로 수동으로 추가된 클래스가 React 관리 범위에 포함되지 않아 다시 애니메이션이 트리거될 가능성이 생긴다.
이를 통해서 DOM을 직접 조작하는 것보다 해당 애니메이션 상태를 useState와같은 상태로 관리하는 것이 좋다.
2. 3D요소 추가
3D로 스타일링된 Carousel을 만들어보려한다.
아래와 같은 형태의 Carousel을 만들 것이다.
이를 만들려면 원과 삼각법을 이용해 각 요소의 위치를 계산해야한다.
먼저 각 이미지가 원형 배열에서 차지하는 각도인 theta를 계산한다.
원은 360도로 이루어져 있으므로,
imageCount(이미지 갯수)로 나누면 각 이미지가 원형으로 배치되었을 때 서로 떨어진 각도를 계산할 수 있다.
예를 들어 이미지가 9개라면 각 이미지는 원형으로 배치되었을 때 서로 360/9 = 40도씩 떨어져 배치된다.
그리고 원형인 상태에서 어느정도 3D로 떨어져야하는지 필요한 값인 원의 반지름을 구해야한다.
radius는 원형 배열에서의 반지름으로, Carousel의 중심에서 이미지가 배치되는 거리다.
이 값을 삼각법을 사용해 계산한다.
삼각법의 원리:
- 원 중심각에서 각 이미지의 거리 관계:
- 원을 수평으로 잘랐을 때, 각 이미지가 원의 표면에 고르게 배치
- 각 이미지는 중심으로부터 일정한 거리(radius)를 유지하면서 회전
- 삼각비 활용:
- 중심각 θ\theta의 절반을 이용하여 tan(θ/2)\tan(\theta/2)를 계산
- Math.tan(Math.PI / imageCount)는 중심각 theta를 라디안 단위로 변환한 값을 사용
- 여기서 150은 Carousel의 높이를 지정한 것이다.
- 150은 이미지가 회전하며 배치되는 원형 트랙의 "대략적인 크기"를 결정하는 값이다.
- 큰 값일수록 Carousel의 반경이 커지고, 이미지들이 멀리 배치됩니다.
- 작은 값일수록 반경이 작아지고, 이미지들이 더 가까워집니다.
그림으로 표현하면 아래와 같다.
아무튼 이를 통해서 값을 구한 다음
슬라이드 아이템을 감싸고 있는 <ul>에는 아래와 같은 스타일을 지정한다.
먼저 3D를 표현하기위해 translateZ()로 radius 만큼 기준점이 들어가게끔했고,
rotateY()로는 요소를 회전하면서 볼 수 있도록 currentIndex * theta만큼의 각도를 주었다.
그리고 각 슬라이드 아이템인 <li>에는 아래와 같이 스타일이 지정되어야한다.
ul의 기준점을 안쪽으로 이동시켰으니 각 아이템은 실제로 앞으로 와야한다.
그래서 다시 translateZ를 이용해 radius만큼 눈앞으로 올 수 있게 하고,
rotateY로 각 요소가 자신의 위치에서 적절히 회전되어 있도록 합니다.
📘마치며..
Carousel은 이미지 슬라이드와 비슷하지만 로직은 조금 달랐다.
다른 방법을 생각해보려고 했지만
기본적으로 currentIndex라는 상태를 기준으로 동작해야한다는 것을 알았다.
현재 1번 방식에서도 currentIndex에서 다음 index를 가져와서 애니메이션을 구성하고 있다.
만약 데이터를 나열하고, 앞 뒤로 복제된 데이터를 붙이는 방식이라도 currentIndex가 있어야 붙이고 말고를 정할 수 있다고 생각이 들었다.
만약 이런 방식이 아니라면 그건 이미지 슬라이드 방식이라고 생각이 들었다.
아무튼 Carousel은 순회가 가능해야한다는 점이 중요하기 때문에 그 부분을 생각하고 로직을 작성해야 했다.
그리고 터치 이벤트를 꼭 따로 넣어줘야했다.
현재는 넣지 않았지만 만약 넣는다면
터치이벤트 순서를 이용해 이동 좌표를 비교해서 오른쪽인지 왼쪽인지만 판단하면 될 것 같았다.
'React' 카테고리의 다른 글
DropDownBox 기능을 구현하면서 (0) | 2025.01.04 |
---|---|
이미지 슬라이드를 구현하면서 (0) | 2024.12.15 |
팝오버를 구현하면서 (1) | 2024.12.06 |
Modal을 구현하는 방법 - Context API, CreatePortal, Dialog (1) | 2024.11.28 |
SnackBar를 구현하면서...- Context API, CreatePortal (0) | 2024.11.22 |