No.160
MODAL CSSJS

Lightbox Gallery

サムネイルを押すと拡大表示が開き、矢印ボタンや←→キーで前後の画像へ送れるライトボックス。Esc で閉じ、閉じたら押したサムネイルへフォーカスを戻します。

DEMO

    CODE

    HTML
    <div class="demo-stage lbx-stage" id="lbx-stage">
      <ul class="lbx-grid" id="lbx-grid" aria-label="ギャラリー"></ul>
      <div class="lbx-view" id="lbx-view" role="dialog" aria-modal="true" aria-label="拡大表示" hidden>
        <div class="lbx-photo" id="lbx-photo"></div>
        <p class="lbx-cap" id="lbx-cap" aria-live="polite"></p>
        <button type="button" class="lbx-btn lbx-prev" id="lbx-prev" aria-label="前の画像">‹</button>
        <button type="button" class="lbx-btn lbx-next" id="lbx-next" aria-label="次の画像">›</button>
        <button type="button" class="lbx-btn lbx-close" id="lbx-close" aria-label="閉じる">×</button>
      </div>
    </div>
    CSS
    .lbx-stage { position:relative; display:flex; align-items:center; justify-content:center; padding:1.5rem; background:#0B0B10; min-height:320px; overflow:hidden; }
    .lbx-grid { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(3,1fr); gap:.6rem; width:100%; max-width:420px; }
    .lbx-thumb { display:block; width:100%; aspect-ratio:4/3; border:0; border-radius:10px; cursor:zoom-in; transition: transform .2s ease, filter .2s ease; }
    .lbx-thumb:hover { transform:scale(1.03); filter:brightness(1.1); }
    .lbx-thumb:focus-visible, .lbx-btn:focus-visible { outline:2px solid #fff; outline-offset:3px; }
    .lbx-view { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:.7rem; background:rgba(5,5,8,.9); animation: lbx-fade .2s ease both; }
    .lbx-view[hidden] { display:none; }
    .lbx-photo { width:min(76%, 380px); aspect-ratio:4/3; border-radius:12px; box-shadow:0 20px 50px -10px rgba(0,0,0,.8); animation: lbx-zoom .28s cubic-bezier(.22,1,.36,1) both; }
    .lbx-cap { margin:0; color:#C9C9D1; font-size:.8rem; font-variant-numeric:tabular-nums; }
    .lbx-btn { position:absolute; width:44px; height:44px; border:0; border-radius:50%; background:rgba(255,255,255,.1); color:#fff; font-size:1.4rem; line-height:1; cursor:pointer; }
    .lbx-btn:hover { background:rgba(255,255,255,.2); }
    .lbx-prev { left:.6rem; top:50%; margin-top:-22px; }
    .lbx-next { right:.6rem; top:50%; margin-top:-22px; }
    .lbx-close { top:.6rem; right:.6rem; font-size:1.1rem; }
    @keyframes lbx-fade { from { opacity:0; } to { opacity:1; } }
    @keyframes lbx-zoom { from { opacity:0; transform:scale(.9); } to { opacity:1; transform:none; } }
    @media (prefers-reduced-motion: reduce) {
      .lbx-view, .lbx-photo { animation:none; }
      .lbx-thumb { transition-duration:.01ms; }
    }
    JS
    (function(){
      var grid = document.getElementById('lbx-grid');
      var view = document.getElementById('lbx-view');
      var photo = document.getElementById('lbx-photo');
      var cap = document.getElementById('lbx-cap');
      if (!grid || !view || !photo) return;
      var ITEMS = [
        ['Dawn', 'linear-gradient(135deg,#F9A8D4,#E91B89)'], ['Dusk', 'linear-gradient(135deg,#7B37A8,#1E1B4B)'],
        ['Sea', 'linear-gradient(135deg,#5BAADE,#0F766E)'], ['Forest', 'linear-gradient(135deg,#34D399,#065F46)'],
        ['Sand', 'linear-gradient(135deg,#FDE68A,#D97706)'], ['Night', 'linear-gradient(135deg,#334155,#0B0B10)']
      ];
      var cur = 0, opener = null;
      var thumbs = ITEMS.map(function(it, i){
        var li = document.createElement('li');
        var b = document.createElement('button');
        b.type = 'button'; b.className = 'lbx-thumb'; b.style.background = it[1];
        b.setAttribute('aria-label', it[0] + 'を拡大表示');
        b.addEventListener('click', function(){ open(i, b); });
        li.appendChild(b); grid.appendChild(li);
        return b;
      });
      function show(i){
        cur = (i + ITEMS.length) % ITEMS.length;
        photo.style.background = ITEMS[cur][1];
        photo.style.animation = 'none'; void photo.offsetWidth; photo.style.animation = '';
        cap.textContent = ITEMS[cur][0] + ' ' + (cur + 1) + ' / ' + ITEMS.length;
      }
      function open(i, btn){ opener = btn; view.hidden = false; show(i); document.getElementById('lbx-close').focus(); }
      function close(){ view.hidden = true; (thumbs[cur] || opener).focus(); }
      document.getElementById('lbx-prev').addEventListener('click', function(){ show(cur - 1); });
      document.getElementById('lbx-next').addEventListener('click', function(){ show(cur + 1); });
      document.getElementById('lbx-close').addEventListener('click', close);
      view.addEventListener('click', function(e){ if (e.target === view) close(); });
      view.addEventListener('keydown', function(e){
        if (e.key === 'Escape') { e.preventDefault(); close(); }
        else if (e.key === 'ArrowLeft') { e.preventDefault(); show(cur - 1); }
        else if (e.key === 'ArrowRight') { e.preventDefault(); show(cur + 1); }
        else if (e.key === 'Tab') {
          var f = view.querySelectorAll('button'), first = f[0], last = f[f.length - 1];
          if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
          else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
        }
      });
    })();

    AI PROMPT

    Claude Code Cursor v0
    PROMPT
    サムネイルを押すと拡大表示が開き、前後の画像へ送れるライトボックス(Lightbox)を作って。サムネイルは button 要素にして「〇〇を拡大表示」の aria-label を付け、grid で3列に並べる(ホバーで scale(1.03)・cursor: zoom-in)。拡大表示は role="dialog" aria-modal="true" の要素で、背景を暗くして .2秒でフェードイン、画像は scale(.9) から .28秒で拡大して現れる。前へ・次へ・閉じるボタン(44px 以上・aria-label 付き)を置き、←→キーでも送れるようにし、最後の次は最初へ戻る。キャプションに「名前 2 / 6」の形で位置を表示し、aria-live="polite" で読み上げに伝える。画像を切り替えるたびに拡大アニメーションを再生し直す(animation を none にして void offsetWidth で強制リフローしてから戻す)。開いたら閉じるボタンにフォーカスを移し、Tab と Shift+Tab でフォーカスがダイアログの外へ出ないように端で折り返す。Esc と背景クリックで閉じ、閉じたら最後に表示していた画像のサムネイルへフォーカスを戻す。@media (prefers-reduced-motion: reduce) ではアニメーションを無効化する。
    
    Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

    AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

    BOOKS · PR · AMAZON

    📖 なかしまぁ先生のCSSアニメーション入門 📖 UIデザインの心理学 ―わかりやすさ・使いやすさの法則 📖 Fundamentals of Web Animation with GSAP

    ※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

    学びを止めない · AMAZONサブスクPR

    📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

    ※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

    CREATOR TOOLS · スクール(PR)

    🎬 Adobe Creative Cloud — After Effects / Photoshop 業界標準ツール → 💻 SkillHacks — プログラミング・動画編集オンラインスクール → 🚀 デイトラ — Web制作スクール・実案件レベルまで習得 →

    ※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。

    RELATED EFFECTS

    ↗ MORE
    → CROSSFADE GALLERY → EXPANDABLE CARD → MODAL SCALE BLUR → INVOKER COMMANDS

    Lightbox(ライトボックス)とは

    サムネイルを押すと、画面の上に画像を大きく表示し、前後の画像へ送れるようにする表示方法です。写真のポートフォリオ、ECの商品画像、施工事例の一覧などで、ページを移動せずに画像を大きく見せたい時の定番です。

    当サイトの Crossfade Gallery は同じ場所で画像を切り替える演出、Expandable Card はカードがその場で詳細に広がる演出、Modal Scale Blur は情報を見せるモーダルです。本ページは画像の拡大と前後送りに特化した別テーマで、キーボードだけでも一覧から拡大・送り・閉じるまで操作できます。

    品質を分けるのは操作の作法です。←→キーでの送り、Esc で閉じる、開いている間はフォーカスを外へ出さない、閉じたら一覧の位置へ戻す、の4つがそろうと、マウスを使わない人にも使いやすいギャラリーになります。

    よくある質問

    閉じたあと、どのサムネイルにフォーカスを戻すべきですか?

    開いたサムネイルではなく、最後に表示していた画像のサムネイルへ戻すのが自然です。拡大表示の中で画像を送った場合、利用者の関心は最後に見ていた画像にあるためです。本実装は現在の番号のサムネイルへフォーカスを戻し、一覧の中の位置を見失わないようにしています。

    実際の写真を使う場合に気をつけることは?

    拡大表示では大きな画像を読み込むため、サムネイルには小さい画像、拡大表示には大きい画像と、別々のファイルを用意してください。拡大用の画像は開いた時に初めて読み込み、読み込み中は同じ大きさの枠を先に表示しておくと、表示の途中でレイアウトが跳ねません。画像には内容を表す代替テキストも必ず付けてください。

    コードは無料で使えますか?

    はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。