画像ギャラリーをモーダルで表示
サムネイル画像をクリックすると、拡大画像がモーダルで表示されるギャラリー機能を実装します。
シンプルなHTML・CSS・JavaScriptだけで構成し、閉じるボタンや背景クリックでもモーダルを閉じられる仕様です。
そのままコピペで利用できる実用的なサンプルコードです。
コードについて
本記事のコードは AI(ChatGPT)による生成をベースに作成・調整しています。ご利用の環境でテストの上ご使用ください。
免責
本コードの利用に伴う不具合・損害について、当サイトは責任を負いません。自己責任にてご利用ください。
デモ
下のサムネイル画像をクリックすると、モーダルで拡大画像が表示されます。
表示中は左右のボタンで前後の画像に移動でき、背景クリックや「×」ボタン、Escキーで閉じることができます。
モーダルが開いている間は背景のスクロールも停止する仕様です。
コードをコピーして使おう!
/* ▼CSS(#gallery-section スコープ推奨)
- すべてのスタイルを #gallery-section 内に限定して
他のページ要素と衝突しないようにしています
*/
/* サムネイル一覧のレイアウト */
#gallery-section .gallery-grid{
display:grid; /* グリッドレイアウトを使用 */
grid-template-columns: repeat(auto-fill, minmax(120px,1fr)); /* 最小120pxで自動分割 */
gap:12px; /* サムネイル間の余白 */
margin:12px 0 20px; /* 上下に余白を追加 */
}
/* サムネイル要素(個々の枠) */
#gallery-section .thumb{
position:relative; /* 子要素(ボタン)を絶対配置するため */
border-radius:10px; /* 角丸 */
overflow:hidden; /* 画像が枠からはみ出さないように */
background:#f1f5f9; /* 画像が遅延読み込み中でも背景色が見えるように */
aspect-ratio:4/3; /* 横4:縦3の比率を維持 */
}
/* サムネイル画像 */
#gallery-section .thumb img{
width:100%; height:100%; /* 枠いっぱいに拡大縮小 */
object-fit:cover; /* 枠に合わせて切り取り */
display:block; /* 行間の余白を消すためブロック化 */
transition: transform .2s ease, opacity .2s ease; /* ホバー時のアニメーション */
}
/* サムネイル画像のホバー演出(少し拡大&透明度ダウン) */
#gallery-section .thumb:hover img{
transform:scale(1.025);
opacity:.9;
}
/* サムネイル上の透明ボタン
- img 要素の上に invisible ボタンを置いてクリック可能にする
*/
#gallery-section .thumb button{
position:absolute; inset:0; /* 親の枠いっぱいに広げる */
background:transparent; border:none; /* 背景・枠を消す */
cursor:pointer; /* マウスカーソルを指マークに */
}
/* モーダル背景(オーバーレイ部分) */
#gallery-section .overlay{
position:fixed; inset:0; /* 画面全体に固定配置 */
background:rgba(2,8,23,.82); /* 半透明の黒に近い背景 */
display:none; /* 初期は非表示 */
place-items:center; /* 中央に配置(grid特性) */
z-index:9999; /* 最前面に表示 */
}
/* 表示状態(.active が付与された時) */
#gallery-section .overlay.active{
display:grid;
}
/* モーダル内のラッパー(ライトボックス本体) */
#gallery-section .lightbox{
position:relative; /* 子要素のボタン配置の基準にする */
max-width:min(92vw,920px); /* 画面幅の92%か最大920px */
max-height:88vh; /* 画面高の88%を上限にする */
}
/* 拡大画像 */
#gallery-section .lightbox img{
display:block;
max-width:100%; max-height:88vh; /* ラッパー内で縮小 */
border-radius:12px; /* 角丸 */
box-shadow:0 24px 60px rgba(0,0,0,.35); /* 影を付ける */
background:#fff; /* 透過画像に備えて白背景 */
}
/* 閉じるボタン */
#gallery-section .lb-close{
position:absolute; top:10px; right:10px; /* 右上に固定 */
border:none; background:#fff; color:#0f172a; /* 白地に濃紺文字 */
padding:6px 10px; border-radius:8px; /* 適度な余白と角丸 */
cursor:pointer;
box-shadow:0 2px 8px rgba(0,0,0,.2); /* 少し浮いているような影 */
}
/* ホバー時に色を薄グレーに */
#gallery-section .lb-close:hover{
background:#f1f5f9;
}
/* 前後ナビゲーションボタン */
#gallery-section .lb-prev,
#gallery-section .lb-next{
position:absolute; top:50%; transform:translateY(-50%); /* 垂直中央に配置 */
border:none; background:#fff; color:#0f172a; /* 白地に濃紺文字 */
padding:10px 12px; border-radius:999px; /* 丸ボタンにする */
cursor:pointer;
box-shadow:0 2px 8px rgba(0,0,0,.25); /* 影を付けて浮かせる */
}
/* 左右の位置 */
#gallery-section .lb-prev{ left:10px; }
#gallery-section .lb-next{ right:10px; }
/* ホバー時に背景色をグレーに */
#gallery-section .lb-prev:hover,
#gallery-section .lb-next:hover{
background:#f1f5f9;
}
/* キャプション(画像タイトルなどを表示) */
#gallery-section .lb-caption{
margin-top:10px;
color:#e2e8f0; /* 淡いグレーで落ち着いた印象に */
text-align:center;
font-size:12px;
}
/* 背景スクロール抑止
- モーダルが開いているときに body に .modal-lock を付けて
ページ全体のスクロールを止める
*/
body.modal-lock{
overflow:hidden;
}
<!-- ▼HTML(#gallery-section スコープ推奨) -->
<div id="gallery-section" aria-label="画像ギャラリー">
<!-- サムネイル一覧 -->
<div class="gallery-grid">
<div class="thumb">
<img src="https://picsum.photos/seed/gal1/600/400" alt="森と湖(サンプル1)">
<button type="button" data-large="https://picsum.photos/seed/gal1/1200/800"></button>
</div>
<div class="thumb">
<img src="https://picsum.photos/seed/gal2/600/400" alt="夕焼けの海(サンプル2)">
<button type="button" data-large="https://picsum.photos/seed/gal2/1200/800"></button>
</div>
<div class="thumb">
<img src="https://picsum.photos/seed/gal3/600/400" alt="街並みの夜景(サンプル3)">
<button type="button" data-large="https://picsum.photos/seed/gal3/1200/800"></button>
</div>
</div>
<!-- モーダル -->
<div class="overlay" aria-hidden="true">
<div class="lightbox" role="dialog" aria-modal="true" aria-labelledby="lb-caption">
<button type="button" class="lb-close" aria-label="閉じる">×</button>
<button type="button" class="lb-prev" aria-label="前の画像へ">‹</button>
<button type="button" class="lb-next" aria-label="次の画像へ">›</button>
<img id="lb-img" src="" alt="">
<p id="lb-caption" class="lb-caption"></p>
</div>
</div>
</div>
// ▼JavaScript(#gallery-section スコープ推奨)
(function(){
// ---------- 要素の取得 ----------
const root = document.getElementById('gallery-section'); // ギャラリー全体のラッパー
const thumbs = Array.from(root.querySelectorAll('.thumb')); // サムネイル要素を配列化
const overlay = root.querySelector('.overlay'); // モーダル背景(オーバーレイ)
const imgEl = root.querySelector('#lb-img'); // モーダル内の拡大画像
const captionEl = root.querySelector('#lb-caption'); // モーダル下のキャプション(alt表示)
const btnClose = root.querySelector('.lb-close'); // 閉じるボタン
const btnPrev = root.querySelector('.lb-prev'); // 「前へ」ボタン
const btnNext = root.querySelector('.lb-next'); // 「次へ」ボタン
// ---------- 状態管理 ----------
let current = -1; // 現在表示しているサムネイルのインデックス(初期値:なし)
let lastFocus = null; // モーダルを開く前にフォーカスしていた要素を保存
// ---------- 指定インデックスの画像を開く ----------
function openAt(index){
const thumb = thumbs[index];
if(!thumb) return; // 該当なしなら終了
current = index; // 現在のインデックスを更新
const large = thumb.querySelector('button').dataset.large; // ボタンの data-large 属性から大きな画像URL取得
const alt = thumb.querySelector('img').alt || ''; // サムネイル画像の alt テキスト取得
imgEl.src = large; // 拡大画像を差し替え
imgEl.alt = alt; // alt テキストを設定(A11y対策)
captionEl.textContent = alt; // キャプションとして alt を表示
overlay.classList.add('active'); // モーダルを表示
overlay.setAttribute('aria-hidden','false'); // アクセシビリティ属性を更新
document.body.classList.add('modal-lock'); // 背景スクロールを止める
lastFocus = document.activeElement; // 現在のフォーカス要素を記録
btnClose.focus(); // モーダル表示後は閉じるボタンにフォーカスを移動
}
// ---------- モーダルを閉じる ----------
function close(){
overlay.classList.remove('active'); // モーダルを非表示
overlay.setAttribute('aria-hidden','true'); // アクセシビリティ属性を更新
document.body.classList.remove('modal-lock'); // 背景スクロールを再開
imgEl.removeAttribute('src'); // 画像をクリア(不要なロード防止)
if(lastFocus) lastFocus.focus(); // 元のフォーカス位置に戻す
}
// ---------- 前後移動 ----------
function showPrev(){
if(current>=0) openAt((current-1+thumbs.length)%thumbs.length); // 前の画像へ(ループ対応)
}
function showNext(){
if(current>=0) openAt((current+1)%thumbs.length); // 次の画像へ(末尾なら先頭にループ)
}
// ---------- イベント登録 ----------
thumbs.forEach((t,idx)=>{
const btn = t.querySelector('button'); // サムネイル上の透明ボタン
btn.addEventListener('click',()=>openAt(idx)); // クリックで該当画像を開く
});
btnClose.addEventListener('click', close); // 閉じるボタンクリックで閉じる
btnPrev.addEventListener('click', showPrev); // 前ボタンで前の画像へ
btnNext.addEventListener('click', showNext); // 次ボタンで次の画像へ
// 背景(overlay)のクリック時に閉じる(ただし画像やボタン部分は除く)
overlay.addEventListener('click', e=>{
if(e.target===overlay) close();
});
// キーボード操作対応
window.addEventListener('keydown', e=>{
if(!overlay.classList.contains('active')) return; // モーダル非表示中は無視
if(e.key==='Escape') close(); // Escキー → モーダルを閉じる
if(e.key==='ArrowLeft') showPrev(); // ←キー → 前の画像
if(e.key==='ArrowRight') showNext(); // →キー → 次の画像
});
})();
コピペで完結!モーダルウインドウ#13【画像ギャラリーをモーダルで表示】
コメント