Construire une galerie d'images avec Claude Code
Découvrez construire une galerie d'images avec Claude Code. Conseils pratiques et exemples de code inclus.
画像ギャラリーをClaude Codeで構築する
画像ギャラリーはポートフォリオ、ECサイト、写真共有アプリなど、多くの場面で必要になるコンポーネントです。Claude Codeを使えば、マソンリーレイアウト、ライトボックス、遅延読み込みを備えた高品質なギャラリーを効率的に構築できます。
ギャラリーの要件を指示する
> 画像ギャラリーコンポーネントを作って。
> マソンリーレイアウト、ライトボックス表示、
> 遅延読み込み、カテゴリフィルタリングを実装して。
> レスポンシブ対応で。
マソンリーレイアウト
// src/components/MasonryGallery.tsx
'use client';
import { useState, useMemo } from 'react';
interface GalleryImage {
id: string;
src: string;
alt: string;
width: number;
height: number;
category: string;
}
interface Props {
images: GalleryImage[];
columns?: number;
}
export function MasonryGallery({ images, columns = 3 }: Props) {
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
const categories = useMemo(
() => [...new Set(images.map((img) => img.category))],
[images]
);
const filteredImages = useMemo(
() => selectedCategory
? images.filter((img) => img.category === selectedCategory)
: images,
[images, selectedCategory]
);
// カラムに画像を分配
const columnImages = useMemo(() => {
const cols: GalleryImage[][] = Array.from({ length: columns }, () => []);
filteredImages.forEach((img, i) => {
cols[i % columns].push(img);
});
return cols;
}, [filteredImages, columns]);
return (
<div>
{/* カテゴリフィルター */}
<div className="flex gap-2 mb-6 flex-wrap">
<button
onClick={() => setSelectedCategory(null)}
className={`px-4 py-2 rounded-full text-sm ${
!selectedCategory ? 'bg-blue-600 text-white' : 'bg-gray-100 dark:bg-gray-800'
}`}
>
すべて
</button>
{categories.map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className={`px-4 py-2 rounded-full text-sm ${
selectedCategory === cat ? 'bg-blue-600 text-white' : 'bg-gray-100 dark:bg-gray-800 dark:text-gray-300'
}`}
>
{cat}
</button>
))}
</div>
{/* マソンリーグリッド */}
<div className="flex gap-4">
{columnImages.map((col, colIndex) => (
<div key={colIndex} className="flex-1 flex flex-col gap-4">
{col.map((img) => (
<div
key={img.id}
onClick={() => setLightboxIndex(filteredImages.indexOf(img))}
className="cursor-pointer overflow-hidden rounded-lg hover:opacity-90 transition"
>
<img
src={img.src}
alt={img.alt}
loading="lazy"
className="w-full h-auto"
style={{ aspectRatio: `${img.width}/${img.height}` }}
/>
</div>
))}
</div>
))}
</div>
{/* ライトボックス */}
{lightboxIndex !== null && (
<Lightbox
images={filteredImages}
currentIndex={lightboxIndex}
onClose={() => setLightboxIndex(null)}
onNavigate={setLightboxIndex}
/>
)}
</div>
);
}
ライトボックスコンポーネント
// src/components/Lightbox.tsx
'use client';
import { useEffect, useCallback } from 'react';
interface Props {
images: GalleryImage[];
currentIndex: number;
onClose: () => void;
onNavigate: (index: number) => void;
}
export function Lightbox({ images, currentIndex, onClose, onNavigate }: Props) {
const current = images[currentIndex];
const handleKeyDown = useCallback((e: KeyboardEvent) => {
switch (e.key) {
case 'Escape':
onClose();
break;
case 'ArrowLeft':
if (currentIndex > 0) onNavigate(currentIndex - 1);
break;
case 'ArrowRight':
if (currentIndex < images.length - 1) onNavigate(currentIndex + 1);
break;
}
}, [currentIndex, images.length, onClose, onNavigate]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = '';
};
}, [handleKeyDown]);
return (
<div
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center"
onClick={onClose}
>
<button onClick={onClose} className="absolute top-4 right-4 text-white text-3xl z-10">
×
</button>
{currentIndex > 0 && (
<button
onClick={(e) => { e.stopPropagation(); onNavigate(currentIndex - 1); }}
className="absolute left-4 text-white text-4xl hover:text-gray-300"
>
‹
</button>
)}
<img
src={current.src}
alt={current.alt}
className="max-h-[90vh] max-w-[90vw] object-contain"
onClick={(e) => e.stopPropagation()}
/>
{currentIndex < images.length - 1 && (
<button
onClick={(e) => { e.stopPropagation(); onNavigate(currentIndex + 1); }}
className="absolute right-4 text-white text-4xl hover:text-gray-300"
>
›
</button>
)}
<div className="absolute bottom-4 text-white text-sm">
{currentIndex + 1} / {images.length}
</div>
</div>
);
}
パフォーマンス最適化
画像のパフォーマンスを高めるために、以下のポイントをClaude Codeに追加で依頼しましょう。
- srcset属性: デバイス幅に応じた画像サイズの提供
- BlurHash: 読み込み中のプレースホルダー表示
- WebP/AVIF: 次世代フォーマットの自動変換
- CDN配信: CloudflareやCloudFrontでのキャッシュ
関連記事
画像処理全般は画像処理の実装、パフォーマンス最適化はパフォーマンス最適化ガイドも参考にしてください。
画像最適化にはCloudinary(cloudinary.com)のようなサービスの活用も検討するとよいでしょう。
Related Posts
Comment booster vos projets personnels avec Claude Code [Avec exemples]
Apprenez à accélérer considérablement vos projets de développement personnels avec Claude Code. Inclut des exemples concrets et un workflow pratique de l'idée au déploiement.
Comment automatiser le refactoring avec Claude Code
Apprenez à automatiser efficacement le refactoring de code avec Claude Code. Inclut des prompts pratiques et des patterns de refactoring concrets pour des projets réels.
Guide complet de configuration CORS avec Claude Code
Découvrez le guide complet de configuration CORS avec Claude Code. Conseils pratiques et exemples de code inclus.