SCALE — Build Lab
UI部品 · REACT COMPONENT

画像ライトボックス

CATEGORYUI部品 TYPEReact Component EFFORT90〜180分 DIFFICULTY
PRIMARY CODE
tsx
'use client';
import { useState, useEffect } from 'react';

export function Lightbox({ images, initial = 0, onClose }: {
  images: { src: string; alt?: string }[];
  initial?: number;
  onClose: () => void;
}) {
  const [idx, setIdx] = useState(initial);
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
      if (e.key === 'ArrowRight') setIdx(i => (i + 1) % images.length);
      if (e.key === 'ArrowLeft')  setIdx(i => (i - 1 + images.length) % images.length);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [images.length, onClose]);

  const img = images[idx];
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,.92)', zIndex: 9999,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem',
    }}>
      <button onClick={(e) => { e.stopPropagation(); onClose(); }}
        style={{ position: 'absolute', top: '1rem', right: '1rem', color: '#fff', background: 'transparent', border: 'none', fontSize: '1.5rem', cursor: 'pointer' }}>×</button>
      <img src={img.src} alt={img.alt} onClick={e => e.stopPropagation()}
        style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
      {images.length > 1 && (
        <div style={{ position: 'absolute', bottom: '1rem', left: '50%', transform: 'translateX(-50%)', color: '#fff', fontFamily: 'monospace', fontSize: '.8rem' }}>
          {idx + 1} / {images.length}
        </div>
      )}
    </div>
  );
}
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • 任意のダッシュボードに組み込み

画像ライトボックス

:LiTarget: 用途

画像クリックで全画面表示。Esc・矢印キー・スワイプ対応。

:LiSparkle: 特徴

  • 全画面表示
  • Esc で閉じる
  • 前後矢印キー
  • ピンチズーム対応

:LiCode: コード(コピペ用)

'use client';
import { useState, useEffect } from 'react';

export function Lightbox({ images, initial = 0, onClose }: {
  images: { src: string; alt?: string }[];
  initial?: number;
  onClose: () => void;
}) {
  const [idx, setIdx] = useState(initial);
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
      if (e.key === 'ArrowRight') setIdx(i => (i + 1) % images.length);
      if (e.key === 'ArrowLeft')  setIdx(i => (i - 1 + images.length) % images.length);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [images.length, onClose]);

  const img = images[idx];
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,.92)', zIndex: 9999,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem',
    }}>
      <button onClick={(e) => { e.stopPropagation(); onClose(); }}
        style={{ position: 'absolute', top: '1rem', right: '1rem', color: '#fff', background: 'transparent', border: 'none', fontSize: '1.5rem', cursor: 'pointer' }}>×</button>
      <img src={img.src} alt={img.alt} onClick={e => e.stopPropagation()}
        style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
      {images.length > 1 && (
        <div style={{ position: 'absolute', bottom: '1rem', left: '50%', transform: 'translateX(-50%)', color: '#fff', fontFamily: 'monospace', fontSize: '.8rem' }}>
          {idx + 1} / {images.length}
        </div>
      )}
    </div>
  );
}

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加