[코드잇 스프린트] 고급 프로젝트 (배너)

이언덕·2025년 6월 21일
post-thumbnail

🖼️ 배너 영역과 무한 슬라이드 기능 분석

📌 배너 영역: API 연결부터 상태 관리, 렌더링까지

🧩 관련 상태 (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 재적용 트릭.



🖥️ 무한 슬라이드 결과물



✅ 전체 흐름 요약

  1. getActivities로 API에서 데이터를 받아와 activities에 저장.
  2. activities를 바탕으로 JSX에서 배너 UI 구성.
  3. extendedActivities를 만들어 무한 슬라이드 구현.
  4. setInterval로 자동 전환 처리.
  5. 마지막 슬라이드일 경우 currentIndex = 0으로 리셋 + 트랜지션 제거 후 복구.

이렇게 설계된 구조는 자연스럽고 부드러운 UX를 제공하며, 반응형 구조와도 잘 맞물려 있다.

0개의 댓글