Lightbox

@reelkit/react-lightbox を使った、フルスクリーンの画像と動画のギャラリーのコンポーネントです。

ライブデモを見る →

機能

画像と動画
動画スライドを標準でサポート
タッチジェスチャー
スワイプで移動
スワイプで閉じる
上にスワイプして閉じる
キーボード操作
矢印キーと Escape
フルスクリーン
ブラウザー間で動く API
トランジション
スライド、フェード、フリップ、ズームイン
先読み
隣の画像を事前に取得
サウンド切り替え
スライドごとのミュート
読み込み状態
スピナーと独自の描画
エラー処理
エラーアイコンと独自の描画
レンダープロップ
カスタマイズできる 6 つの領域
フック
useVideoSlideRenderer と useFullscreen
URL の状態
共有もブックマークもできるリンク
スライドショー
オプトインの自動送り、再生と一時停止に対応
ダウンロード
アクティブなアイテムを保存するオプトインのボタン

インストール

bash
npm install @reelkit/react-lightbox @reelkit/react lucide-react

スタイルのインポートを忘れないでください:

typescript
import '@reelkit/react-lightbox/styles.css';
アイコン

デフォルトのコントロールはアイコンに lucide-react を使います。別のアイコンライブラリを使いたい場合は、renderControls と renderNavigation で独自のものを渡してください。

クイックスタート

LightboxOverlay コンポーネントは画像をフルスクリーンで表示します。LightboxItem オブジェクトの配列を渡し、null を取りうるインデックスで表示を制御します。

tsx
import { useState } from 'react';
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: 'https://example.com/image1.jpg',
    title: 'Sunset',
    description: 'Beautiful sunset over the ocean',
  },
  {
    src: 'https://example.com/image2.jpg',
    title: 'Mountains',
  },
];

function App() {
  const [index, setIndex] = useState<number | null>(null);

  return (
    <>
      {images.map((img, i) => (
        <img
          key={i}
          src={img.src}
          onClick={() => setIndex(i)}
        />
      ))}
      <LightboxOverlay
        isOpen={index !== null}
        images={images}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
      />
    </>
  );
}
LightboxPage.tsx
import { useState } from 'react';
import {
  LightboxOverlay,
  flipTransition,
  lightboxFadeTransition,
  lightboxZoomTransition,
  slideTransition,
  type LightboxItem,
} from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';
import '@reelkit/react-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: '/cdn/samples/images/image-01.jpg',
    title: 'Mountain River',
    description: 'A beautiful mountain river flowing through the forest',
    width: 1600,
    height: 1000,
  },
  {
    src: '/cdn/samples/images/image-02.jpg',
    title: 'Snowy Peaks',
    description: 'Majestic snow-capped mountains reaching for the sky',
    width: 1000,
    height: 1600,
  },
  {
    src: '/cdn/samples/images/image-03.jpg',
    title: 'Foggy Forest',
    description: 'Misty morning in the dense forest',
    width: 1600,
    height: 900,
  },
  {
    src: '/cdn/samples/images/image-04.jpg',
    title: 'Ocean Waves',
    description: 'Powerful ocean waves crashing against the rocky shore',
    width: 900,
    height: 1400,
  },
  {
    src: '/cdn/samples/images/image-05.jpg',
    title: 'Autumn Path',
    description: 'A winding path through the autumn forest',
    width: 1600,
    height: 1067,
  },
  {
    src: '/cdn/samples/images/image-06.jpg',
    title: 'Coastal Cliffs',
    description: 'Dramatic coastal cliffs overlooking the deep blue sea',
    width: 1600,
    height: 1067,
  },
];

const transitions: { label: string; fn: TransitionTransformFn }[] = [
  { label: 'slide', fn: slideTransition },
  { label: 'fade', fn: lightboxFadeTransition },
  { label: 'flip', fn: flipTransition },
  { label: 'zoom-in', fn: lightboxZoomTransition },
];

export default function App() {
  const [index, setIndex] = useState<number | null>(null);
  const [transitionFn, setTransitionFn] = useState<TransitionTransformFn>(
    () => slideTransition,
  );

  return (
    <div style={{ padding: 16, background: '#f8fafc', minHeight: '100vh' }}>
      {/* Transition picker */}
      <div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>
        {transitions.map((t) => (
          <button
            key={t.label}
            onClick={() => setTransitionFn(() => t.fn)}
            style={{
              padding: '6px 14px', borderRadius: 8, border: 'none', cursor: 'pointer',
              fontSize: 13, fontWeight: 500,
              background: transitionFn === t.fn ? '#6366f1' : '#e2e8f0',
              color: transitionFn === t.fn ? '#fff' : '#334155',
            }}
          >
            {t.label}
          </button>
        ))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
        {images.map((img, i) => (
          <button
            key={i}
            onClick={() => setIndex(i)}
            style={{
              position: 'relative', aspectRatio: '4 / 3', borderRadius: 8,
              overflow: 'hidden', border: 'none', padding: 0, cursor: 'pointer',
              background: '#e2e8f0',
            }}
          >
            <img
              src={img.src}
              alt={img.title}
              style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
            />
          </button>
        ))}
      </div>
      <LightboxOverlay
        isOpen={index !== null}
        images={images}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
        transitionFn={transitionFn}
      />
    </div>
  );
}

ギャラリーをスライドショーとして再生し、各写真の元ファイルを保存する方法はギャラリーのスライドショーのレシピを参照してください。

動画スライド(オプトイン)

動画のサポートは完全にオプトインで、ツリーシェイキングできます。画像だけを使う場合、バンドルは 1 バイトも増えません。useVideoSlideRenderer をインポートし、その戻り値を LightboxOverlay につないでください。読み込み状態、サウンドの管理、動画のライフサイクルはフックが自動で処理します。

tsx
import {
  LightboxOverlay,
  useVideoSlideRenderer,
  type LightboxItem,
} from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

const items: LightboxItem[] = [
  { src: '/photo.jpg', title: 'Photo' },
  {
    src: '/clip.mp4',
    type: 'video',
    poster: '/clip-thumb.jpg',
    title: 'Video Clip',
  },
];

function Gallery() {
  const [index, setIndex] = useState<number | null>(null);
  const isOpen = index !== null;
  const { renderSlide, renderControls, SoundProvider } =
    useVideoSlideRenderer(items);

  return (
    <SoundProvider>
      {/* thumbnails… */}
      <LightboxOverlay
        isOpen={isOpen}
        images={items}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
        renderSlide={renderSlide}
        renderControls={renderControls}
      />
    </SoundProvider>
  );
}
仕組み
  • フックは SoundProvider を返します。ミュートを機能させるには、オーバーレイをこれで包んでください
  • スライドがアクティブになると、動画が自動再生されます(デフォルトはミュート)
  • iOS で音声を途切れさせないよう、共有の動画要素がスライド間で再利用されます
  • 動画スライドには、リアクティブなミュート切り替えのサウンドボタンが自動で表示されます
  • type: 'video' のないアイテムは画像として描画されます(後方互換)

カスタマイズ

独自のコントロール

renderControls で、デフォルトの閉じるボタン、カウンター、フルスクリーン切り替えを置き換えます。公開されているサブコンポーネントと組み合わせてください:

tsx
import {
  LightboxOverlay,
  CloseButton,
  Counter,
  FullscreenButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderControls={({ onClose, activeIndex, count, isFullscreen, onToggleFullscreen }) => (
    <div style={{ position: 'absolute', top: 12, left: 16, right: 16, display: 'flex', justifyContent: 'space-between', zIndex: 10 }}>
      <Counter currentIndex={activeIndex} count={count} />
      <div>
        <FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />
        <CloseButton onClick={onClose} />
      </div>
    </div>
  )}
/>

独自の情報オーバーレイ

renderInfo でデフォルトのタイトルと説明のグラデーションを置き換えるか、renderInfo={() => null} を渡して完全に非表示にします:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderInfo={({ item, index }) => (
    <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, background: 'linear-gradient(transparent, rgba(0,0,0,0.8))', color: '#fff', zIndex: 10 }}>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </div>
  )}
/>

独自のナビゲーション

renderNavigation で、デフォルトの前後の矢印を置き換えます:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderNavigation={({ onPrev, onNext, activeIndex, count }) => (
    <div style={{ position: 'absolute', bottom: 24, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 12, zIndex: 10 }}>
      <button onClick={onPrev} disabled={activeIndex === 0}>Prev</button>
      <span>{activeIndex + 1} / {count}</span>
      <button onClick={onNext} disabled={activeIndex === count - 1}>Next</button>
    </div>
  )}
/>

独自のスライド

renderSlide でスライドの内容を完全に独自のものにします。デフォルトの画像スライドに戻すには null を返します:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderSlide={({ item, index, size, isActive, onReady, onError }) => {
    // Custom CTA on last slide
    if (index === images.length - 1) {
      return (
        <div style={{ width: size[0], height: size[1], display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
          <h2>View all photos</h2>
        </div>
      );
    }
    return null; // default image slide
  }}
/>

コンテンツの読み込みとエラー処理

ギャラリーはスライドごとの読み込み中とエラーの状態を追跡します。読み込み中はスピナーを、失敗したメディアには壊れた画像のアイコンを表示します。エラーになった URL はキャッシュされるので、再訪すると再試行せずにすぐエラーを表示します。

ライフサイクルのコールバック

renderSlide を使う場合は、これらのコールバックを呼んで読み込み表示を制御します:

コールバック呼ぶタイミング
onReady画像が読み込まれたか、動画の再生が始まったとき。読み込み中とエラーの状態をクリアします。
onWaiting再生の途中で動画がバッファリングしているとき。読み込み表示を出します。
onErrorコンテンツの読み込みに失敗したとき。エラーのオーバーレイを出し、その URL を壊れたものとしてキャッシュします。
tsx
// Inside renderSlide — wire callbacks to your custom media
renderSlide={({ item, index, size, isActive, onReady, onWaiting, onError }) => (
  <div style={{ width: size[0], height: size[1] }}>
    {item.type === 'video' ? (
      <video
        src={item.src}
        poster={item.poster}
        autoPlay={isActive}
        onCanPlay={onReady}
        onWaiting={onWaiting}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
      />
    ) : (
      <img
        src={item.src}
        onLoad={onReady}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
      />
    )}
  </div>
)}

独自の読み込みとエラーの UI

デフォルトのスピナーとエラーアイコンを、独自のコンポーネントに置き換えます:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={() => setIsOpen(false)}
  renderLoading={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: '#fff', fontSize: 14,
    }}>
      Loading image {activeIndex + 1}...
    </div>
  )}
  renderError={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      gap: 12, color: 'rgba(255,255,255,0.5)',
    }}>
      <span style={{ fontSize: 48 }}>!</span>
      <span>Failed to load content</span>
    </div>
  )}
/>

URL の状態

ライブデモを見る →

LightboxUrlOverlay は、開閉状態をアドレスバーに持つ別のコンポーネントです。@reelkit/react の useOverlayUrlState でコントローラーを作り、controller として渡します。パラメーターがスライドを指すとギャラリーは自分で開き、パラメーターがなくなると閉じます。リンクは共有でき、戻るボタンはページを離れる代わりにギャラリーを閉じます。

組み込みのキー

スライドは組み込みのキーで指せます。urlIndexKey(位置で指す)か urlStableIdKey(安定した id で指す)をコントローラーに展開してください。どちらも @reelkit/react から再エクスポートされています。URL の状態のガイド と Core API を参照してください。

tsx
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/react';
import { LightboxUrlOverlay } from '@reelkit/react-lightbox';
import { Link } from 'react-router-dom';

const photo = useOverlayUrlState({
  param: 'photo',
  ...urlIndexKey(() => images.length),
});

// Opening is a link — the href is the open action. No open flag, no handler:
// the overlay reads the URL and opens itself.
{images.map((image, i) => (
  <Link key={image.src} to={`?photo=${i}`}>
    <img src={image.src} />
  </Link>
))}

<LightboxUrlOverlay controller={photo} images={images} />

フックは 1 つのオプションのオブジェクトを受け取り、UrlStateController(set、index、value を持ちます)を返します。プログラムから制御するために手元に残してください。set はオーバーレイが内部で使う低レベルの書き込みです(スライドの切り替えと、set(null) で閉じる処理)。プログラムからオーバーレイを操作することもでき、set(index) はパラメーターへ移動するのと同じように開きます。ただし、開く操作にはリンクを使うほうがよいでしょう。href は共有でき、新しいタブで開け、戻るボタンで閉じられます。ハンドラーなしで、すべてがそのまま手に入ります。

useOverlayUrlState のすべてのオプション(param、adapter、codec、locator)は React API リファレンス を参照してください。

LightboxUrlOverlay 自体が受け取るのは controller(必須)、任意の onClose、そして LightboxOverlay が受け取るすべての見た目と挙動の props(images、ariaLabel、transitionFn、各レンダープロップなど)です。isOpen はありません。

  • 開くと履歴エントリーを 1 つ使います。スライドを送るとそれを置き換えるので、100 回スワイプしてもエントリーは増えず、1 回戻れば必ずギャラリーから出られます。戻るとギャラリーが閉じ、写真は戻りません。
  • ?photo=3 のような共有リンクは、そのスライドでギャラリーを開きます。ページと一緒に届いたリンクを閉じると、サイトから離れるのではなく、その場でパラメーターを取り除きます。
  • 戻るで閉じるのは、アプリ内から開いた場合だけです。 リンクがエントリーを積んだので、戻るとギャラリーの前へ戻ります。新しいタブで直接開いた共有リンクには前の履歴がないので、ブラウザーの戻るではサイトを離れます。閉じるボタンか Escape なら、その場でパラメーターを取り除いてギャラリーにとどまります。同梱のルーターアダプターは同じページでのリンクの push を保証します。エントリーの経緯を伝えられない独自のアダプターでもその場で閉じる動作になり、履歴にページの重複が残ります。その場合 1 回戻っても何も起きないように見えますが、再び開くことはありません。
  • どのスライドも指さないパラメーター(古いブックマークや手で書き換えた値)は、開けないスライドをアドレスバーが指したままにならないよう、URL から取り除かれます。

ルーターを使うアプリでは、アダプターを渡します。 履歴に直接書き込むとルーター自身の位置情報が古くなり、次のナビゲーションでパラメーターが消えます。

tsx
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';

const adapter = useReactRouterUrlAdapter();
const photo = useOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => images.length),
});

<LightboxUrlOverlay controller={photo} images={images} />

開く操作はリンクです。 開閉状態が URL にあるので、サムネイルはただのリンクで、クリックハンドラーはいりません。新しいタブで開く、アドレスをコピーする、ホバーでプレビューするといったブラウザー本来の動作がそのまま使えます。ルーターを使うアプリでは、クライアント側で遷移するよう、ルーターのリンクを使ってください。

tsx
import { Link } from 'react-router-dom';

// The href is the open action — no onClick, no open flag.
{images.map((image, i) => (
  <Link key={image.src} to={`?photo=${i}`}>
    <img src={image.src} />
  </Link>
))}

<LightboxUrlOverlay controller={photo} images={images} />

共有リンクには安定した識別子を使いましょう。 インデックスは位置なので、ブックマークした ?photo=3 はリストが並べ替えられると別の画像を開きます。urlStableIdKey は各アイテムの安定した id で指し、現在のリストを走査します。よくあるケースは 1 回の呼び出しで済みます。

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  ...urlStableIdKey({ items: () => images }),
});

<LightboxUrlOverlay controller={photo} images={images} />

hashCodec: base64UrlCodec を渡すと、URL の id を base64url でエンコードします。元に戻せる難読化で、暗号学的ハッシュではありません。

別のフィールド(slug)で指したい場合や、locateAsync で無限フィードをページングしたい場合は、codec/locator を自分で作ります:

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.slug === id),
    identify: (index) => images[index].slug,
  },
});

<LightboxUrlOverlay controller={photo} images={images} />

無限またはページングされたギャラリー。 同期の locate は読み込み済みの画像にしか答えられません。20 件を読み込んだフィードの 400 番目の画像への共有リンクは見つかりません。locateAsync はフォールバックで、locate が見つけられなかったときだけ呼ばれます。

近道

アイテムの id で指すなら、codec と locator を手書きする必要はありません。locateAsync をそのまま urlStableIdKey({ items, locateAsync }) に渡してください(見つからなければ取得してからインデックスを返します)。下のより完全な例は、別のフィールドで指す場合や、すべてを自分で制御したい場合のものです。

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.id === id),
    identify: (index) => images[index].id,
    locateAsync: async (id) => {
      const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
      if (!loaded) return null; // exhausted — link names no image
      setImages(loaded); // commit — the overlay renders from this state
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});

<LightboxUrlOverlay controller={photo} images={images} />
  • 読み込み方はあなた次第です。目的の位置まで連続したページを取得しても、その 1 枚だけを取得して追加してもかまいません。URL は位置ではなく識別子で指すので、findIndex はアイテムが置かれた場所を返します。
  • locateAsync が保留中のあいだ、ギャラリーは閉じたままで、パラメーターはそのまま残るので、ディープリンクは取得のあいだも失われません。null か reject でパラメーターは取り除かれます。
  • URL が変わったあと、閉じたあと、アンマウントしたあとに届いた答えは破棄されます。遅い取得が、誰も求めていないスライドを開くことはありません。
  • 保留中は何も描画されません。その読み込み状態はすでにページが持っているので、独自のスケルトンを描画してください。
  • タイムアウトはありません。ギャラリーには自分の長さがわからないからです。ページングが尽きたら null で決着をつけてください。そうしないとオーバーレイは閉じたままになります。
  • locateAsync が返したものが正です。取得したばかりのデータのインデックスとして、images を読み直さずにそのまま使われます。

自動送り

autoAdvance を指定すると、ギャラリーが開いたときにスライドショーとして再生します。デフォルトではオフなので、既存の Lightbox の描画と動作は変わりません。

tsx
import { LightboxOverlay } from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  autoAdvance
  autoAdvanceInterval={4000}
/>;
  • 画像は autoAdvanceInterval(デフォルトは 5000 ms)のあいだ表示されます。画像の読み込みが終わった時点から数えるので、遅い画像が誰にも見られないまま飛ばされることはありません。読み込みに失敗した画像は、そのエラーの時点から数えます。
  • 動画はループせずに 1 回だけ再生し、終わると次へ進みます。useVideoSlideRenderer を使えば自動で組み込まれます。独自の renderSlide では props から autoAdvance を読み、終わったことを onEnded で伝えてください。
  • 待機するのは、マウスがコントロール(ボタン、リンク、キャプション)の上にあるあいだ、スワイプ中、ページが非表示のあいだです。そのあとは止まったところから続けます。
  • 手で移動すると(矢印、キー、ホイール、スワイプ)、新しいスライドの間隔は 0 から始まります。スライドショーはオンのままです。
  • 最後のスライドでは、loop が指定されていない限り止まります。コントロールの再生/一時停止ボタンが状態を示し、閲覧者が切り替えられます。

独自の renderControls は isAutoAdvancing を受け取り、autoAdvance がオンのときだけ onToggleAutoAdvance も受け取ります。それがあるときは PlayPauseButton を描画してください。タイミング自体は @reelkit/core の createSlideshowController です。同じ動作を自分のプレイヤーに組み込むには自動再生ガイドを、メソッドについては Core API を参照してください。

ダウンロード

showDownload を指定すると、コントロールにダウンロードボタンが加わります。アクティブなアイテムを downloadUrl(なければ src)から、downloadName(なければ URL の最後の部分)という名前で保存します。

tsx
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';

const images: LightboxItem[] = [
  {
    src: 'https://cdn.example.com/beach-1600.jpg',
    // Save the original rather than the resized preview
    downloadUrl: 'https://cdn.example.com/beach-original.jpg',
    downloadName: 'beach.jpg',
  },
];

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  showDownload
/>;

ファイルは fetch してから保存します。別オリジンのリンクでは、ブラウザーは download 属性を無視してファイルを開いてしまうからです。fetch が失敗したとき(よくあるのは CORS ヘッダーを送らない別オリジンのサーバーです)は、ファイルを新しいタブで開くので、閲覧者はそこから保存できます。ワンクリックで保存できるようにするには、ファイルを Access-Control-Allow-Origin 付きで配信してください。ダウンロード中はボタンが無効になります。

独自の renderControls は isDownloading を受け取り、showDownload がオンのときだけ onDownload も受け取ります。それがあるときは DownloadButton を描画してください。ヘルパーは @reelkit/core の downloadFile です。ダウンロードガイドを参照してください。

API リファレンス

LightboxOverlay の Props

LightboxOverlayProps

Prop型デフォルト説明
isOpenboolean必須ギャラリーの表示を制御します。URL で開閉を決めたい場合は、別のコンポーネントの LightboxUrlOverlay を使ってください(下の URL の状態を参照)。
imagesLightboxItem[]必須表示する画像の配列
ariaLabelstring'Image gallery'ダイアログ領域のアクセシブルなラベル。ギャラリーが開くとスクリーンリーダーが読み上げます
initialIndexnumber0開始時の画像のインデックス
transitionFnTransitionTransformFnslideTransitionスライドのトランジション関数。組み込みのもの(slideTransition、flipTransition、lightboxFadeTransition、lightboxZoomTransition)をインポートするか、独自のものを渡します。省略すると slideTransition になります。
autoAdvancebooleanfalseギャラリーをスライドショーとして再生します。自動送りを参照してください。
autoAdvanceIntervalnumber5000スライドショーで各画像を表示しておく長さ(ミリ秒)。読み込みが終わった時点から数えます
showDownloadbooleanfalseアクティブなアイテムのダウンロードボタンを加えます。ダウンロードを参照してください。
apiRefMutableRefObject<ReelApi>-Reel の API にアクセスするための ref
renderControls(props: ControlsRenderProps) => ReactNode-独自のコントロール。デフォルトの閉じるボタン、カウンター、フルスクリーン切り替えを置き換えます
renderNavigation(props: NavigationRenderProps) => ReactNode-独自のナビゲーション。デフォルトの前後の矢印を置き換えます
renderInfo(props: InfoRenderProps) => ReactNode-独自の情報オーバーレイ。デフォルトのタイトルと説明のグラデーションを置き換えます。非表示にするには null を返します。
renderSlide(props: SlideRenderProps) => ReactNode | null-独自のスライド描画。{ item, index, size, isActive, autoAdvance, onReady, onWaiting, onError, onEnded } を受け取ります。デフォルトに戻すには null を返します。
renderLoading(props: { item: LightboxItem; activeIndex: number }) => ReactNode-独自の読み込み表示。デフォルトのスピナーを置き換えます
renderError(props: { item: LightboxItem; activeIndex: number }) => ReactNode-独自のエラー表示。デフォルトのエラーアイコンを置き換えます

LightboxUrlOverlay の Props

LightboxUrlOverlayProps

上の見た目と挙動の props をすべて受け取りますが、isOpen の代わりに controller を使います。ここでは initialIndex は無視されます。スライドはコントローラーの位置で決まるので、一緒に渡した値は開くたびに上書きされてしまいます。

Prop型デフォルト説明
controllerUrlStateController必須useOverlayUrlState のコントローラー。その位置で、オーバーレイが開いているかどうかと、どのスライドを表示するかが決まります。オーバーレイはスライドの切り替え時と閉じるときに、これを通じて書き戻します。

コールバック

Prop型説明
onClose() => voidギャラリーが閉じたときに呼ばれます。LightboxOverlay では必須です(開閉状態を持つのはあなたなので、閉じる処理も必要です)。LightboxUrlOverlay では任意で、閉じるのは URL が行うので、閉じたあとに反応したいときだけ渡します。
onSlideChange(index: number) => voidスライドが切り替わったあとに呼ばれます

Reel の Props(引き継ぎ)

これらの props は、内部の Reel コンポーネントに渡されます。型は ReelProxyProps で、オーバーレイが引き継ぐ Reel の props の一部です。それらを転送するラッパーの型付けに使います。

Prop型デフォルト説明
loopbooleanfalse無限ループを有効にします
enableNavKeysbooleantrueキーボード操作を有効にします
enableWheelbooleantrueマウスホイールでの操作を有効にします
wheelDebounceMsnumber200ホイールのデバウンス時間(ms)
transitionDurationnumber300トランジションのアニメーションの長さ(ms)
swipeDistanceFactornumber0.12スワイプのしきい値(0-1)
swipeToCloseDirection'up' | 'down''up'モバイルでスワイプして閉じるジェスチャーの方向

型

LightboxItem

typescript
interface LightboxItem {
  src: string;
  type?: 'image' | 'video';  // defaults to 'image'
  poster?: string;            // thumbnail for video items
  title?: string;
  description?: string;
  width?: number;
  height?: number;
  downloadUrl?: string;       // what the download button saves; defaults to src
  downloadName?: string;      // file name; defaults to the URL's last segment
}

ControlsRenderProps

typescript
interface ControlsRenderProps {
  item: LightboxItem;
  activeIndex: number;
  count: number;
  isFullscreen: boolean;
  isAutoAdvancing: boolean;
  isDownloading: boolean;
  onClose: () => void;
  onToggleFullscreen: () => void;
  onToggleAutoAdvance?: () => void; // only with autoAdvance
  onDownload?: () => void;          // only with showDownload
}
typescript
interface NavigationRenderProps {
  item: LightboxItem;
  activeIndex: number;
  count: number;
  onPrev: () => void;
  onNext: () => void;
}

SlideRenderProps

typescript
interface SlideRenderProps {
  item: LightboxItem;
  index: number;
  size: [number, number];
  isActive: boolean;
  autoAdvance: boolean; // a video should play once, not loop
  onReady: () => void;
  onWaiting: () => void;
  onError: () => void;
  onEnded: () => void;  // a video played to its end
}

InfoRenderProps

typescript
interface InfoRenderProps {
  item: LightboxItem;
  index: number;
}

サブコンポーネント

renderControls で独自のコントロールを組み立てるための、再利用できるサブコンポーネントです。

CloseButton

デフォルトの X の閉じるボタンです。Props(CloseButtonProps)は onClick、オプションの className と style です。

tsx
import { CloseButton } from '@reelkit/react-lightbox';

<CloseButton onClick={onClose} />

Counter

「1 / 3」と表示する画像のカウンターです。Props(CounterProps)は currentIndex(0 始まり)、count、オプションの className と style です。

tsx
import { Counter } from '@reelkit/react-lightbox';

<Counter currentIndex={activeIndex} count={count} />

FullscreenButton

フルスクリーンの切り替えボタンです(最大化/最小化のアイコン)。Props(FullscreenButtonProps)は isFullscreen、onToggle、オプションの className と style です。

tsx
import { FullscreenButton } from '@reelkit/react-lightbox';

<FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />

SoundButton

動画スライド用のミュート切り替えボタンです(Volume2/VolumeX のアイコン)。useVideoSlideRenderer の renderControls には自動で含まれます。独自のコントロールの中で単独で使う場合は、useSoundState でサウンドの状態にアクセスしてください。Props(SoundButtonProps)は isMuted、onToggle、オプションの className と style です。

tsx
import { SoundButton } from '@reelkit/react-lightbox';
import { useSoundState } from '@reelkit/react';

// Inside a component wrapped in SoundProvider:
function CustomControls({ onClose }) {
  const soundState = useSoundState();

  return (
    <div>
      <SoundButton
        isMuted={soundState.muted.value}
        onToggle={soundState.toggle}
      />
      <button onClick={onClose}>Close</button>
    </div>
  );
}

PlayPauseButton

スライドショーの切り替えボタンです(Play/Pause のアイコン、再生中は aria-pressed)。Props(PlayPauseButtonProps)は isPlaying、onToggle、オプションの className と style です。デフォルトのコントロールは autoAdvance がオンのときに表示します。

tsx
import {
  LightboxOverlay,
  PlayPauseButton,
  CloseButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  autoAdvance
  renderControls={({ isAutoAdvancing, onToggleAutoAdvance, onClose }) => (
    <>
      {onToggleAutoAdvance && (
        <PlayPauseButton
          isPlaying={isAutoAdvancing}
          onToggle={onToggleAutoAdvance}
        />
      )}
      <CloseButton onClick={onClose} />
    </>
  )}
  {...props}
/>;

DownloadButton

ダウンロードボタンです(Download のアイコン)。isDownloading のあいだは無効になります。Props(DownloadButtonProps)は isDownloading、onClick、オプションの className と style です。デフォルトのコントロールは showDownload がオンのときに表示します。

tsx
import {
  LightboxOverlay,
  DownloadButton,
  CloseButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  showDownload
  renderControls={({ isDownloading, onDownload, onClose }) => (
    <>
      {onDownload && (
        <DownloadButton isDownloading={isDownloading} onClick={onDownload} />
      )}
      <CloseButton onClick={onClose} />
    </>
  )}
  {...props}
/>;

フック

useVideoSlideRenderer

オプトインの動画サポートのためのフックです。renderSlide、renderControls、SoundProvider を返します。オーバーレイを SoundProvider で包み、描画関数を渡してください。戻り値の型は UseVideoSlideRendererResult で、どれかの要素が type: 'video' のとき true になる hasVideo も持っています。

その renderSlide は、動画の要素を LightboxVideoSlide で描画します。これは共有の <video> 要素 1 つで再生する動画スライドで、iOS でも音が鳴り続け、動画ごとの再生位置を覚えています。自分で描画するのは独自の renderSlide からだけにしてください。Props(LightboxVideoSlideProps)は src、poster、isActive、size、slideKey(保存した位置とキャプチャしたフレームのキー)、loop(デフォルト true)と、onPlaying、onWaiting、onError、onEnded のコールバックです。

typescript
import { useVideoSlideRenderer } from '@reelkit/react-lightbox';

const { renderSlide, renderControls, SoundProvider, hasVideo } =
  useVideoSlideRenderer(items);

// SoundProvider  — wrap LightboxOverlay in this for mute/unmute support
// renderSlide    — pass to LightboxOverlay's renderSlide prop
// renderControls — pass to LightboxOverlay's renderControls prop
//                  (includes Counter, FullscreenButton, SoundButton, CloseButton)
// hasVideo       — true if items contain at least one video

useFullscreen

ブラウザー間で動くフルスクリーンの状態を管理するフックです。

tsx
import { useRef } from 'react';
import { useFullscreen } from '@reelkit/react';

function CustomLightbox() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isFullscreen, requestFullscreen, exitFullscreen, toggleFullscreen] =
    useFullscreen({ ref: containerRef });

  return (
    <div ref={containerRef}>
      <button onClick={toggleFullscreen}>
        {isFullscreen.value ? 'Exit Fullscreen' : 'Enter Fullscreen'}
      </button>
    </div>
  );
}

トランジション

任意の TransitionTransformFn を transitionFn プロップで渡します。使うトランジションだけをインポートすれば、残りはバンドラーがツリーシェイキングします。省略すると slideTransition になります。

関数提供元説明
slideTransition@reelkit/react-lightbox標準的な横方向のスライド(デフォルト)
lightboxFadeTransition@reelkit/react-lightbox画像どうしをクロスフェード
flipTransition@reelkit/react-lightbox3D でカードをめくる効果
lightboxZoomTransition@reelkit/react-lightbox小さいサイズから通常のサイズへズームイン
tsx
import {
  LightboxOverlay,
  lightboxFadeTransition,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  initialIndex={0}
  onClose={handleClose}
  transitionFn={lightboxFadeTransition}
/>

独自のトランジション関数

独自の TransitionTransformFn を書き、transitionFn で渡します。シグネチャはコアのスライダーのトランジションと同じです。

tsx
import { LightboxOverlay } from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';

const customFade: TransitionTransformFn = (offset, size) => ({
  transform: `translate3d(${offset * size[0]}px, 0, 0)`,
  opacity: 1 - Math.min(Math.abs(offset), 1),
});

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  transitionFn={customFade}
  onClose={() => setIsOpen(false)}
/>

CSS クラス

UI 要素はすべて通常の CSS クラス(CSS Modules ではありません)を使うので、@reelkit/react-lightbox/styles.css のあとに読み込むスタイルシートで、詳細度の高いセレクターを使って指定できます。色、サイズ、z-index を変えるなら、下の テーマ設定 で説明している CSS カスタムプロパティを使ってください。

クラスコンポーネント説明
.rk-lightbox-overlayOverlayルートのコンテナー(フルスクリーンの背景)
.rk-lightbox-spinnerOverlayデフォルトの読み込みスピナー
.rk-lightbox-img-errorOverlayエラー状態のコンテナー(壊れた画像や動画)
.rk-lightbox-img-error-textOverlayエラー状態のテキストラベル
.rk-lightbox-swipe-hintOverlayモバイルでのスワイプのヒント
.rk-lightbox-controls-leftControls左上のコントロールのコンテナー
.rk-lightbox-btnControlsコントロールのボタン(フルスクリーンなど)
.rk-lightbox-play-pauseControlsスライドショーの再生/一時停止ボタン(.rk-lightbox-btn と併用)
.rk-lightbox-downloadControlsダウンロードボタン(.rk-lightbox-btn と併用)
.rk-lightbox-closeControls閉じるボタン
.rk-lightbox-counterControls画像のカウンターのチップ
.rk-lightbox-navNavigationナビゲーションの矢印(両方)
.rk-lightbox-nav-prevNavigation前へ の矢印
.rk-lightbox-nav-nextNavigation次へ の矢印
.rk-lightbox-infoInfoタイトルと説明のコンテナー
.rk-lightbox-titleInfo画像のタイトル
.rk-lightbox-descriptionInfo画像の説明
.rk-lightbox-slideSlideスライドのコンテナー
.rk-lightbox-imgSlide画像要素
.rk-lightbox-video-containerVideoSlide動画スライドのコンテナー(オプトイン)
.rk-lightbox-video-elementVideoSlide動画要素(オプトイン)
.rk-lightbox-video-posterVideoSlide動画のポスター画像(オプトイン)

テーマ設定

色、サイズ、z-index、トランジションはすべて CSS カスタムプロパティにあります。:root(またはギャラリーの任意の祖先)で 1 つでも複数でも上書きすれば、コンポーネントのソースに触れずにテーマを変えられます。

トークンデフォルト制御するもの
--rk-lightbox-overlay-bg#000フルスクリーンの背景色
--rk-lightbox-overlay-z9999オーバーレイの z-index
--rk-lightbox-top-shade-height80px上部のグラデーションの高さ
--rk-lightbox-top-shade-bglinear-gradient(rgba(0,0,0,0.6), transparent)上部のグラデーションの色
--rk-lightbox-edge-padding16px閉じる、ナビゲーション、左上のコントロールの端からの距離
--rk-lightbox-controls-gap12px左上のコントロールの間隔
--rk-lightbox-transition0.2sボタンのホバーのトランジションの長さ
--rk-lightbox-blur8pxボタンやチップの背景のぼかしの半径
--rk-lightbox-btn-bgrgba(0, 0, 0, 0.5)閉じる、ナビゲーション、小さいボタンのデフォルトの背景
--rk-lightbox-btn-bg-hoverrgba(255, 255, 255, 0.2)閉じる、ナビゲーション、小さいボタンのホバー時の背景
--rk-lightbox-btn-fg#fff閉じる、ナビゲーション、小さいボタンのアイコンの色
--rk-lightbox-btn-size36px小さいボタンの大きさ(フルスクリーン切り替えなど)
--rk-lightbox-btn-disabled-opacity0.5無効になった小さいボタンの不透明度(ダウンロード中)
--rk-lightbox-close-size40px閉じるボタンの大きさ
--rk-lightbox-nav-size48px前後の矢印の大きさ
--rk-lightbox-nav-opacity0.7前後の矢印の通常時の不透明度
--rk-lightbox-counter-fg#fffカウンターのテキストの色
--rk-lightbox-counter-bgrgba(0, 0, 0, 0.5)カウンターのチップの背景
--rk-lightbox-counter-size14pxカウンターの文字サイズ
--rk-lightbox-counter-padding6px 12pxカウンターのチップの余白
--rk-lightbox-counter-radius20pxカウンターのチップの角丸
--rk-lightbox-spinner-size28pxデフォルトのスピナーの幅と高さ
--rk-lightbox-spinner-trackrgba(255, 255, 255, 0.2)スピナーのトラックの色
--rk-lightbox-spinner-fg#fffスピナーのインジケーターの色
--rk-lightbox-spinner-duration0.8sスピナーの回転の長さ
--rk-lightbox-error-fgrgba(255, 255, 255, 0.4)エラーアイコンとテキストの色
--rk-lightbox-error-text-size13pxエラーメッセージの文字サイズ
--rk-lightbox-info-bglinear-gradient(transparent, rgba(0,0,0,0.8))キャプションの背景のグラデーション
--rk-lightbox-info-padding24pxキャプションの内側の余白
--rk-lightbox-title-size18pxタイトルの文字サイズ
--rk-lightbox-description-size14px説明の文字サイズ
--rk-lightbox-info-fg#fffキャプションのテキストの色
--rk-lightbox-hint-fgrgba(255, 255, 255, 0.5)スワイプのヒントのテキストの色
--rk-lightbox-hint-bgrgba(0, 0, 0, 0.3)スワイプのヒントのチップの背景
--rk-lightbox-hint-duration3sスワイプのヒントのフェードイン・アウト全体の長さ
--rk-lightbox-video-bg#000<video> の後ろのレターボックスの背景

下のスニペットを、@reelkit/react-lightbox/styles.css のあとに読み込むスタイルシートに入れてください。

css
/* Brand the lightbox */
:root {
  --rk-lightbox-overlay-bg: #0f172a;
  --rk-lightbox-btn-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-btn-bg-hover: rgba(168, 85, 247, 0.85);
  --rk-lightbox-nav-size: 56px;
  --rk-lightbox-counter-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-info-bg: linear-gradient(
    transparent,
    rgba(99, 102, 241, 0.55) 60%,
    rgba(168, 85, 247, 0.85)
  );
}

アクセシビリティ

オーバーレイのルートはモーダルダイアログ(role="dialog"、aria-modal="true")です。ariaLabel を指定すると、スクリーンリーダーの読み上げが変わります。デフォルトは「Image gallery」です。各スライドは role="group"、aria-roledescription="slide"、aria-label="Image N of M" を持ちます。

ギャラリーは開くとフォーカスを取り込み、閉じるとトリガーに戻します。Tab と Shift+Tab は中のフォーカス可能な要素を循環し、外へ出たフォーカス(外側のクリック、プログラムからのフォーカス)は引き戻されます。@reelkit/core の captureFocusForReturn と createFocusTrap で実装されています。

キーボードショートカット

キー動作
ArrowLeft前の画像
ArrowRight次の画像
Escapeギャラリーを閉じる(フルスクリーン中ならフルスクリーンを終了)