完成イメージ

手順

Next.js プロジェクトを用意します

npx create-next-app@latest my-carousel-app

プロジェクトに必要な依存関係をインストール

Swiperと必要なアイコンライブラリをインストールします

# Swiperのインストール
npm install swiper

# Font Awesome(ナビゲーション用アイコン)のインストール
npm install @fortawesome/react-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons

# 型定義のインストール(TypeScriptを使用する場合)
npm install --save-dev @types/react-fontawesome

コンポーネントの実装

my-nextjs-app/
├── src/
│   ├── app/
│   │   └── page.tsx          # メインページ
│   ├── components/
│   │   └── ReactSwiper.tsx   # カルーセルコンポーネント
│   └── styles/
│       └── reactSwiper.module.css  # コンポーネント用スタイル
├── package.json
└── ...その他の設定ファイル

ReactSwiper.tsx # カルーセルコンポーネント

"use client";
import React from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination, Autoplay } from "swiper/modules";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faChevronLeft,
  faChevronRight,
} from "@fortawesome/free-solid-svg-icons";
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import styles from "@/styles/reactSwiper.module.css";

interface CarouselProps {
  images?: string[];
}

const ReactSwiper: React.FC<CarouselProps> = ({ images }) => { 
  // 画像がない場合のデフォルト画像
  const defaultImages = [
    "https://bizlabo.site/assets/sample-img/column1.jpg",
    "https://bizlabo.site/assets/sample-img/column2.jpg",
    "https://bizlabo.site/assets/sample-img/column3.jpg",
    "https://bizlabo.site/assets/sample-img/column4.jpg",
    "https://bizlabo.site/assets/sample-img/column5.jpg",
    "https://bizlabo.site/assets/sample-img/column6.jpg",
    "https://bizlabo.site/assets/sample-img/column7.jpg",
    "https://bizlabo.site/assets/sample-img/column8.jpg",
  ];

  const imagesToUse = images || defaultImages;

  return (
    <div className={styles.container}>
      <Swiper
        modules={[Navigation, Pagination, Autoplay]}
        // spaceBetween={window.innerWidth * 0.1} // スライド間のスペース
        // slidesOffsetBefore={window.innerWidth * 0.1} // スライドの前に10%のオフセットを追加
        // slidesOffsetAfter={window.innerWidth * 0.1} // スライドの後に10%のオフセットを追加
        navigation={{
          nextEl: `.${styles.swiperButtonNext}`, // 次へボタン
          prevEl: `.${styles.swiperButtonPrev}`, // 前へボタン
        }}
        pagination={{
          clickable: true, // ページネーションをクリック可能に
          bulletClass: styles.bullet,
          bulletActiveClass: styles.bulletActive,
        }}
        // autoplay={{ delay: 3000 }}
        loop
        centeredSlides // スライドを中央に表示
        slidesPerView={1}
        breakpoints={{
          0: {
            slidesPerView: 1.4,
          },
          768: {
            slidesPerView: 2.2,
          },
          1024: {
            slidesPerView: 4,
          },
        }}
        speed={800}
        className={styles.reactSwiper} // Swiperコンポーネントにクラス名を追加
      >
        {imagesToUse.map((image, index) => (
          <SwiperSlide key={index} className={styles.slide}>
            <img
              src={image}
              alt={`Slide ${index}`}
              className={styles.slideImage}
            />
          </SwiperSlide>
        ))}
        <div className={styles.swiperButtonPrev}>
          <FontAwesomeIcon icon={faChevronLeft} />
        </div>
        <div className={styles.swiperButtonNext}>
          <FontAwesomeIcon icon={faChevronRight} />
        </div>
      </Swiper>
    </div>
  );
};

export default ReactSwiper;

reactSwiper.module.css # コンポーネント用スタイル

.container {
  position: relative;
  padding: 2rem 0;
  overflow: hidden;
}

.slide {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 90%;
}

.slideImage {
  width: 90%;
  height: auto;
  cursor: pointer;
}

.swiperButtonPrev,
.swiperButtonNext {
  position: absolute;
  top: calc(50% - 20px);
  transform: translateY(-50%);
  z-index: 2;
  cursor: pointer;
  color: #000;
  font-size: 4vw;
  transition: color 0.3s ease;
  background-color: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  width: 6vw;
  height: 6vw;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: all 0.3s ease;
}

.swiperButtonPrev:hover,
.swiperButtonNext:hover {
  color: #666;
  background-color: rgba(255, 255, 255, 0.6);
  width: 7vw;
  height: 7vw;
}

.swiperButtonPrev {
  left: 10px;
}

.swiperButtonNext {
  right: 10px;
}

.bullet {
  width: 12px;
  height: 12px;
  display: inline-block;
  border-radius: 50%;
  background: #ccc;
  margin: 0 4px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.bullet:hover {
  background: #666;
  width: 14px;
  height: 14px;
}

.bulletActive {
  background: #000;
}

.reactSwiper {
  padding-bottom: 40px;
}

/* .reactSwiper :global(.swiper-slide) {
  opacity: 1;
} */

/* .reactSwiper :global(.swiper-slide:not(.swiper-slide-active)) {
  opacity: 0.2;
} */

.reactSwiper :global(.swiper-pagination) {
  bottom: 0;
}