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)のようなサービスの活用も検討するとよいでしょう。
PDF gratuit : aide-mémoire Claude Code en 5 minutes
Laissez simplement votre e-mail et nous vous enverrons immédiatement l'aide-mémoire A4 en PDF.
Nous traitons vos données avec soin et n'envoyons jamais de spam.
À propos de l'auteur
Masa
Ingénieur passionné par Claude Code. Il gère claudecode-lab.com, un média tech en 10 langues avec plus de 2 000 pages.
Articles similaires
7 vérifications avant de publier chaque jour un article multilingue sur Claude Code
Une checklist pratique pour publier des articles multilingues sur Claude Code chaque jour sans oublier une langue, casser les CTA ou laisser l’ancien contenu en production.
Codex Automations : confier l'analyse, les articles et le deploiement a l'IA
Guide pratique pour utiliser Codex Automations dans une operation de contenu orientee monetisation.
Claude Code × GCP Cloud Functions Guide Complet | Développement Serverless Ultra-Rapide
Optimisez GCP Cloud Functions avec Claude Code. Implémentez des triggers HTTP/Pub/Sub/Firestore, des tests locaux et l'automatisation des déploiements avec des exemples de code réels de l'expérience de Masa.