
🧩 관련 상태 (
useState)const [activities, setActivities] = useState<ActivityType[]>([]); const [currentIndex, setCurrentIndex] = useState(0);
activities: API로 받아온 체험 리스트 데이터.currentIndex: 현재 배너 슬라이드의 인덱스 (슬라이드 위치 조절에 사용).
🔗 API 연결
useEffect(() => { const fetchActivities = async () => { try { const response = await activityService.getActivities({ query: { method: "offset", category: selectedCategory || undefined, sort: sortOption.id, page: currentPage, size: itemsPerPage, }, }); setActivities(response.data.activities); setTotalPages(Math.ceil(response.data.totalCount / itemsPerPage)); } catch (error) { console.error("Error fetching activities:", error); } }; fetchActivities(); }, [selectedCategory, currentPage, sortOption, itemsPerPage]);
activityService.getActivities()를 통해 백엔드 API에서 체험 리스트 데이터를 받아온다.- 성공 시
activities상태에 저장한다.- 해당 API 함수는
모든 체험과 같이 사용한다.
🖼 렌더링 방식
받아온
activities는 다음과 같이 배너 형태로 슬라이드 렌더링 된다.<section className="w-full overflow-hidden relative"> <div id="slider-track" className="flex transition-transform duration-700 ease-in-out" style={{ width: `${activities.length * 100}%`, transform: `translateX(-${currentIndex * (100 / (activities.length || 1))}%)`, }} {extendedActivities.map((activity, index) => ( <div key={`${activity.id}-${index}`} className="..."> <Image src={activity.bannerImageUrl} alt={activity.title} fill className="object-cover" /> <div className="absolute bottom-[90px] ..."> <h2>{activity.title}</h2> <p>1월의 인기 체험 BEST 🔥</p> </div> </div> ))} </div> </section>실제 렌더링 모습은 아래 사진과 같다.
💡
extendedActivities란?const extendedActivities = activities.length > 0 ? [...activities, activities[0]] : [];
- 무한 슬라이드를 구현하기 위해
activities의 첫 번째 요소를 맨 뒤에 추가.- 마지막 → 첫 번째로 전환 시 자연스러운 애니메이션 효과 유도.
🕒 자동 슬라이드 타이머
useEffect(() => { if (activities.length === 0) return; // const interval = setInterval(() => { setCurrentIndex((prevIndex) => { if (prevIndex >= activities.length) { return 1; } return prevIndex + 1; }); }, 4000); // return () => clearInterval(interval); }, [activities]);
- 4초마다
currentIndex를 증가시켜 자동으로 다음 배너로 슬라이드.- 마지막 배너(extended된 항목)까지 가면 1번으로 점프.
⏱ 트랜지션 리셋 처리
useEffect(() => { if (currentIndex === activities.length) { const timeout = setTimeout(() => { setCurrentIndex(0); const slider = document.getElementById("slider-track"); if (slider) { slider.style.transition = "none"; slider.style.transform = `translateX(0%)`; void slider.offsetWidth; slider.style.transition = ""; } }, 700); // return () => clearTimeout(timeout); } }, [currentIndex, activities.length]);
activities.length === currentIndex일 때 → transition 없이 슬라이드 위치를 초기화.void slider.offsetWidth: 리플로우 강제 발생으로 transition 재적용 트릭.
🖥️ 무한 슬라이드 결과물
getActivities로 API에서 데이터를 받아와activities에 저장.activities를 바탕으로 JSX에서 배너 UI 구성.extendedActivities를 만들어 무한 슬라이드 구현.setInterval로 자동 전환 처리.- 마지막 슬라이드일 경우
currentIndex = 0으로 리셋 + 트랜지션 제거 후 복구.이렇게 설계된 구조는 자연스럽고 부드러운 UX를 제공하며, 반응형 구조와도 잘 맞물려 있다.