Next.js 14 (app router), Swiper (Carousel)

ClydeHan·2024년 6월 26일
4

들어가며

Swiper 로고

여행 가이드 프로젝트를 진행하며 Swiper를 사용하게 되었다. Next.js 14 앱 라우터에서의 Swiper 사용법은 크게 어렵지 않다. 이번 포스트에서는 Swiper를 학습하고 사용한 내용을 기록하고 공유한다. 천천히 따라온다면 쉽게 Swiper를 다룰 수 있게 될 것이다.


Swiper 시작하기

Swiper 설치하기

npm install swiper

가져오기 (import)

Swiper

Next.js와 같은 React 기반 프로젝트에서는 Swiper를 React 컴포넌트로 사용해야한다. 그래서 swiper/react에서 Swiper 컴포넌트를 가져와야 한다.

import Swiper from 'swiper/react';

Swiper Modules

Swiper의 모듈은 슬라이더의 특정 기능을 제공하는 독립적인 코드 블록이다. 모듈을 사용하면 Swiper의 기본 기능을 확장하여 다양한 기능(예: 네비게이션, 페이지네이션, 스크롤바 등)을 추가할 수 있다. 필요에 따라 모듈을 선택적으로 가져와 사용할 수 있다.

import { FreeMode, Mousewheel, Navigation, Pagination } from "swiper/modules";
  • FreeMode: 슬라이드를 자유롭게 스크롤할 수 있게 한다.
  • Mousewheel: 마우스 휠을 사용하여 슬라이드를 이동할 수 있게 한다.
  • Navigation: 이전/다음 버튼을 사용하여 슬라이드를 이동할 수 있게 한다.
  • Pagination: 페이지네이션(슬라이드 인디케이터)을 추가한다.

이외에도 많은 특징을 가진 모듈이 존재한다. 자세한 내용은 최하단 참고문헌의 공식문서 링크에서 확인 가능하다.


Swiper CSS

Swiper가 제공하는 스타일을 적용하기 위해서 Swiper를 사용할 때 CSS 파일을 가져와야 한다. Swiper의 슬라이더와 관련된 레이아웃, 애니메이션, 버튼 스타일 등의 기본적인 스타일링이 이 CSS 파일들에 포함되어 있다. 따라서 Swiper를 사용할 때 필요한 모듈과 함께 해당 모듈의 스타일을 가져와야 슬라이더가 올바르게 표시되고 동작한다.

⬇️ Navigation, Pagination 모듈만을 사용했을 경우의 swiper css import

import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';

Swiper bundle

위에서 설명한 방법은 필요한 모듈만을 가져오고, css도 모듈에 맞게 가져와서 사용하는 일반적인 방법이다. 이렇게 번거롭게 하나하나 가져오지 않고도 번들을 사용하면 단 한 번 import만으로 모든 모듈과 css를 사용할 수 있다.

하지만 모든 모듈이 포함되어 있어 파일 크기가 커지고, 이는 초기 로딩 시간에 영향을 줄 수 있게 된다. 또한 프로젝트에서 사용하지 않는 모듈도 포함되어 있어 불필요한 코드가 로드될 수 있다. 이는 최적화 측면에서 바람직하지 않다.

Swiper의 다양한 기능을 테스트하거나 빠르게 설정하고 싶다면 번들 버전을 사용하는 것도 좋은 방법이다.

import Swiper from 'swiper/bundle';
import 'swiper/css/bundle';

Swiper 사용하기

Swiper, SwiperSlide 태그

<Swiper><SwiperSlide>는 함께 사용되어 슬라이더를 구성한다. Swiper는 전체 슬라이더의 컨테이너로서, 슬라이더의 설정과 기능을 제어한다. 반면에 SwiperSlide는 슬라이더의 각 슬라이드를 정의하여, 슬라이더가 어떤 콘텐츠를 포함할지를 결정한다.

<Swiper>

<Swiper>는 Swiper 슬라이더의 기본 컨테이너 컴포넌트이다. 이 컴포넌트는 슬라이더의 설정과 모듈을 정의하고, 슬라이더의 전체 구조를 담당한다. Swiper 컴포넌트는 슬라이더를 초기화하고, 슬라이드 전환, 네비게이션, 페이지네이션 등의 다양한 기능을 제공하는 역할을 한다.

<Swiper
  modules={[Navigation, Pagination]}
  spaceBetween={50}
  slidesPerView={1}
  navigation
  pagination={{ clickable: true }}
>
  {/* SwiperSlide 태그가 여기에 들어온다. */}
</Swiper>

<SwiperSlide>

<SwiperSlide>는 각 슬라이드를 정의하는 컴포넌트이다. Swiper 컨테이너 내에 포함되며, 각 슬라이드의 콘텐츠를 정의한다. SwiperSlide는 슬라이더의 개별 슬라이드를 나타내며, 여러 개의 SwiperSlide를 추가하여 슬라이더를 구성할 수 있다.

<SwiperSlide>
  <div>Slide 1 Content</div>
</SwiperSlide>
<SwiperSlide>
  <div>Slide 2 Content</div>
</SwiperSlide>
<SwiperSlide>
  <div>Slide 3 Content</div>
</SwiperSlide>

두가지 스타일 캐러셀 예시 코드

이번 프로젝트에서 사용했던 Carousel 코드 2개를 공유한다. 위의 설명을 참고하며 예시 코드를 천천히 확인해 보면 어떻게 Swiper를 사용해야 하는지 쉽게 감을 익힐 수 있을 것이다.

🚨 예시 코드가 길기도 하고, 외주 프로젝트이기에 모든 부분을 공개할 수 없어 Swiper 관련 코드만 기록한다.


캐러셀 스타일 예시 (Carousel Demo

"use client";

import Image from "next/image";
import { useState } from "react";
import { FreeMode, Mousewheel, Navigation, Thumbs } from "swiper/modules";
import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
import "swiper/swiper-bundle.css";

export default function Carousel({
  //
}) {
  const [thumbsSwiper, setThumbsSwiper] = useState<SwiperClass | null>(null);
  const [currentSlide, setCurrentSlide] = useState(1);

  const allImages = [
    ...images.map((image) => image.image),
    ...g_reviews.flatMap((review) =>
      review.review_images.map((image) => image.image)
    ),
    ...reviews.flatMap((review) =>
      review.review_images.map((image) => image.image)
    ),
  ];

  const handleSlideChange = (swiper: SwiperClass) => {
    setCurrentSlide(swiper.activeIndex + 1);
  };

  return (
    <div>
        <Swiper
          modules={[Navigation, Thumbs]}
          navigation={{
            nextEl: ".swiper-button-next-custom",
            prevEl: ".swiper-button-prev-custom",
          }}
          thumbs={{
            swiper:
              thumbsSwiper && !thumbsSwiper.destroyed ? thumbsSwiper : null,
          }}
          className="h-[250px]"
          onSlideChange={handleSlideChange}
        >
          {allImages.map((image, index) => (
            <SwiperSlide key={index} className="h-full">
              <div className="w-full h-full relative">
                <Image
                  src={image}
                  alt={`${title} 이미지`}
                  fill
                  sizes="100%"
                  className="object-cover rounded-lg"
                />
              </div>
            </SwiperSlide>
          ))}
          <div className="swiper-button-next-custom absolute top-1/2 right-4 transform -translate-y-1/2 z-10 cursor-pointer">
            <Image
              src=""
              width={40}
              height={40}
              alt="Next"
            />
          </div>
          <div className="swiper-button-prev-custom absolute top-1/2 left-4 transform -translate-y-1/2 z-10 cursor-pointer">
            <Image
              src=""
              width={40}
              height={40}
              alt="Prev"
            />
          </div>
          <div className="absolute top-4 right-4 text-white text-sm font-normal z-10 bg-black bg-opacity-70 rounded-3xl px-[10px] py-[5px]">
            {`${currentSlide} / ${allImages.length}`}
        </Swiper>
      </div>
      <Swiper
        modules={[Thumbs, FreeMode, Mousewheel]}
        onSwiper={setThumbsSwiper}
        spaceBetween={12}
        slidesPerView="auto"
        freeMode={true}
        mousewheel={{ forceToAxis: true }}
        watchSlidesProgress
        className="overflow-x-auto"
      >
        {allImages.map((image, index) => (
          <SwiperSlide key={index} style={{ width: "100px" }}>
            <div className="w-[100px] h-[68px] relative">
              <Image
                src={image}
                alt={`${title} 썸네일 이미지`}
                fill
                sizes="100%"
                className="object-cover cursor-pointer rounded-lg"
              />
            </div>
          </SwiperSlide>
        ))}
      </Swiper>
    </div>
  );
}

캐러셀 스타일 예시 (Carousel Demo

"use client";

import Image from "next/image";

import { FreeMode, Mousewheel, Navigation, Pagination } from "swiper/modules";
import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
import "swiper/swiper-bundle.css";
import { useState } from "react";

export default function Carousel({
  example_tracks,
}) {
  const [activeIndex, setActiveIndex] = useState(0);

  const handleSlideChange = (swiper: SwiperClass) => {
    setActiveIndex(swiper.activeIndex);
  };

  return (
     <Swiper
            modules={[Pagination, FreeMode, Mousewheel, Navigation]}
            mousewheel={{ forceToAxis: true }}
            pagination={{ clickable: true }}
            className="w-full"
            slidesPerView={2.5}
            centeredSlides={true}
            spaceBetween={240}
            onSlideChange={handleSlideChange}
          >
            {example_tracks.map((track, index) => (
              <SwiperSlide key={track.id}>
                <div
                  className={`flex flex-col items-center h-full gap-[16px] mb-2 transition-all duration-300 ${
                    index === activeIndex
                      ? "scale-100 opacity-100"
                      : "scale-75 opacity-50"
                  }`}
                >
                    <Image
                      src={track.track_images.resized_image}
                      alt={track.track_images.content}
                      fill
                      className="object-cover"
                    />
                    <Image
                      src=""
                      alt="track play button"
                      width={76}
                      height={76}
                      className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
                    />

                  <h3 className="text-title3 pb-[30px] text-grayscale-9 text-center w-[250px] truncate mt-6">
                    SWIPER 테스트
                  </h3>
                </div>
              </SwiperSlide>
            ))}
          </Swiper>
  );
};

Next.js 14에서 Swiper를 사용할 때의 주의점

Next.js 14 로고

Next.js 14에서 Swiper를 사용할 때 몇 가지 주의사항이 있다.

클라이언트 측에서만 사용

  • Swiper는 브라우저 환경에서만 동작하므로, use client를 사용하여 컴포넌트가 클라이언트 측에서 실행되도록 설정해야 한다.

스타일시트 포함

  • Swiper의 스타일시트를 반드시 포함해야 한다. 그렇지 않으면 슬라이더가 제대로 동작하지 않을 수 있다.

SSR 주의

  • Next.js는 서버 사이드 렌더링(SSR)을 기본으로 하지만, Swiper는 클라이언트 사이드 라이브러리이므로 컴포넌트가 클라이언트 측에서만 렌더링되도록 주의해야 한다. 이를 위해서는 useEffect와 같은 클라이언트 사이드 훅을 활용할 수 있다.

마치며

과거에 바닐라 자바스크립트로 캐러셀을 구현했던 경험이 있다. 기본적인 캐러셀을 구현하는 것은 어렵지 않았지만, 여전히 고려해야 할 사항이 많고 구현하는 데 시간이 꽤 걸렸다. 특히, 이번 외주 프로젝트에서는 클라이언트가 인디케이터(페이지네이션)를 포함한 캐러셀을 요구했는데, 이를 바닐라 자바스크립트로 구현하려고 하니 작업이 단순하지 않을 것으로 예상 됐다.

외주 프로젝트는 마감 데드라인이 정해져 있었고, Swiper를 사용하면 이러한 복잡한 요구사항도 매우 간단하게 해결할 수 있을 것 같아 선택하게 되었다. 실제로 Swiper를 사용하니 예상했던 것보다 훨씬 빠르고 쉽게 캐러셀을 구현할 수 있었다.


참고문헌

Swiper 공식 문서
Swiper 공식 문서 (swiper-api)

0개의 댓글