完成イメージ
このチュートリアルでは、以下のような機能を持つギャラリーを作成します
ソースファイル
imageGallery.tsx
// src/components/imageGallery.tsx
'use client'
import { useState } from 'react'
import { X, ChevronLeft, ChevronRight } from 'lucide-react' // Lucide Reactのアイコンを使用
import styles from '@/styles/imageGallery.module.css'
const ImageGallery = () => {
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const [currentIndex, setCurrentIndex] = useState<number>(0);
const images = [
"https://bizlabo.site/sample-img/column1.jpg",
"https://bizlabo.site/sample-img/column2.jpg",
"https://bizlabo.site/sample-img/column3.jpg",
"https://bizlabo.site/sample-img/column4.jpg",
"https://bizlabo.site/sample-img/column5.jpg",
"https://bizlabo.site/sample-img/column6.jpg",
"https://bizlabo.site/sample-img/column7.jpg",
"https://bizlabo.site/sample-img/column8.jpg",
];
// 引数はReact.MouseEvent型として、イベントオブジェクトを受け取り、イベント伝播を停止する
const handleNext = (e: React.MouseEvent) => {
e.stopPropagation(); // 親要素へのイベント伝播を停止
setCurrentIndex((prev) => (prev + 1) % images.length); // 画像の数で割った余りを新しいインデックスとするため、画像の数を超えた場合は0に戻る
setSelectedImage(images[(currentIndex + 1) % images.length]);
};
const handlePrev = (e: React.MouseEvent) => {
e.stopPropagation();
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
setSelectedImage(images[(currentIndex - 1 + images.length) % images.length]);
};
return (
<div className={styles.container}>
<div className={styles.grid}>
{images.map((image, index) => (
<img
key={index}
src={image}
alt={`画像 ${index + 1}`}
className={styles.image}
onClick={() => {
setSelectedImage(image);
setCurrentIndex(index);
}}
/>
))}
</div>
{selectedImage && (
<div className={styles.modal} onClick={() => setSelectedImage(null)}>
<div className={styles.modalContent}>
<button className={styles.closeButton} onClick={() => setSelectedImage(null)}>
<X size={32} /> {/* Xアイコン */}
</button>
<button className={styles.prevButton} onClick={handlePrev}>
<ChevronLeft size={40} /> {/* 左矢印アイコン */}
</button>
<img
src={selectedImage}
alt="拡大画像"
className={styles.modalImage}
onClick={(e) => e.stopPropagation()}
/>
<button className={styles.nextButton} onClick={handleNext}>
<ChevronRight size={40} /> {/* 右矢印アイコン */}
</button>
</div>
</div>
)}
</div>
);
};
export default ImageGallery;
imageGallery.module.css
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
}
.modalContent {
position: relative;
max-width: 90vw;
max-height: 90vh;
}
.closeButton {
position: absolute;
top: -40px; /* 画像の上に配置 */
right: -40px; /* 画像の右に配置 */
background: none;
border: none;
color: white;
cursor: pointer;
padding: 8px;
z-index: 1;
transition: all 0.2s;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.closeButton:hover {
background-color: rgba(255, 255, 255, 0.1);
transform: scale(1.1);
}
.prevButton,
.nextButton {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: white;
cursor: pointer;
padding: 16px;
z-index: 1;
transition: all 0.2s;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.prevButton:hover,
.nextButton:hover {
background-color: rgba(255, 255, 255, 0.1);
transform: translateY(-50%) scale(1.1);
}
.prevButton {
left: -60px;
}
.nextButton {
right: -60px;
}
/* 既存のスタイル */
.container {
padding: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.image {
width: 100%;
height: auto;
object-fit: contain;
cursor: pointer;
transition: transform 0.2s;
}
.image:hover {
transform: scale(1.05);
}
.modalImage {
max-height: 90vh;
max-width: 90vw;
object-fit: contain;
}
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(4, 1fr);
}
}
map関数について
map関数の特徴
- 元の配列の各要素に対して処理を行う
- 新しい配列を返す