完成イメージ
手順
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;
}