document.addEventListener("DOMContentLoaded", () => { // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // location gallery // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■ const gallery = document.getElementById('gallery'); const lightbox = document.getElementById('lightbox'); const imgEl = document.getElementById('lb-image'); const titleEl = document.getElementById('lb-title'); const countEl = document.getElementById('lb-count'); const btnPrev = document.getElementById('btn-prev'); const btnNext = document.getElementById('btn-next'); const btnClose = document.getElementById('btn-close'); const backdrop = lightbox.querySelector('[data-close]'); const buttons = Array.from(gallery.querySelectorAll('.panel[data-index]')); const items = buttons.map(btn => ({ src: btn.querySelector('img').src, alt: btn.querySelector('img').alt || '', caption: btn.dataset.caption || btn.querySelector('.caption')?.innerHTML?.trim() || '' })); let current = 0; // 現在のインデックス let lastFocus = null; // フォーカス復帰用 function updateModal(){ const total = items.length; const it = items[current]; imgEl.src = it.src; imgEl.alt = it.alt; titleEl.innerHTML = it.caption || it.alt || ''; countEl.textContent = (current+1) + ' / ' + total; // 前後ボタンの無限ループ(必要に応じてコメントアウトで端止めに) btnPrev.disabled = false; btnNext.disabled = false; } function openModal(index){ lastFocus = document.activeElement; current = index; updateModal(); lightbox.setAttribute('aria-hidden','false'); document.body.style.overflow = 'hidden'; // 初期フォーカス btnClose.focus(); // ESC/矢印/Tab トラップ document.addEventListener('keydown', onKey); } function closeModal(){ lightbox.setAttribute('aria-hidden','true'); document.body.style.overflow = ''; document.removeEventListener('keydown', onKey); if(lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus(); } function prev(){ current = (current - 1 + items.length) % items.length; updateModal(); } function next(){ current = (current + 1) % items.length; updateModal(); } function onKey(e){ switch(e.key){ case 'Escape': closeModal(); break; case 'ArrowLeft': prev(); break; case 'ArrowRight': next(); break; case 'Tab': // 簡易フォーカストラップ(Close/Prev/Next の3つに巡回) const focusables = [btnPrev, btnNext, btnClose]; const idx = focusables.indexOf(document.activeElement); if(e.shiftKey){ // 逆方向 const ni = (idx <= 0 ? focusables.length : idx) - 1; focusables[ni].focus(); e.preventDefault(); } else { const ni = (idx + 1) % focusables.length; focusables[ni].focus(); e.preventDefault(); } break; } } // クリックイベント buttons.forEach((btn, i)=>{ btn.addEventListener('click', ()=> openModal(i)); }); btnPrev.addEventListener('click', prev); btnNext.addEventListener('click', next); btnClose.addEventListener('click', closeModal); backdrop.addEventListener('click', closeModal); // 画像読み込み中のチラつき抑制(任意) imgEl.addEventListener('load', ()=>{ imgEl.style.opacity = 1; }); // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // root gallery // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // 画像をプリロード const preload = (src) => { const i = new Image(); i.src = src; }; // すべてのギャラリーを初期化 document.querySelectorAll('[data-root]').forEach((wrap) => { const mainImg = wrap.querySelector('.main-img'); const mainCap = wrap.querySelector('.main-cap'); const thumbs = wrap.querySelectorAll('.thumb'); // クリックで差し替え const activate = (btn) => { if (!btn || btn.classList.contains('is-active')) return; const nextSrc = btn.dataset.large; const nextAlt = btn.dataset.alt || ''; const nextCap = btn.dataset.caption || ''; // フェードアウト mainImg.classList.add('is-fading'); // 読み込み完了後に差し替え(チラつき防止) const tmp = new Image(); tmp.onload = () => { mainImg.src = nextSrc; mainImg.alt = nextAlt; if (mainCap) mainCap.innerHTML = nextCap; thumbs.forEach(t => t.classList.remove('is-active')); btn.classList.add('is-active'); // フェードイン requestAnimationFrame(() => { mainImg.classList.remove('is-fading'); }); }; tmp.src = nextSrc; // 次候補も軽くプリロード(任意) const btnIdx = Array.from(thumbs).indexOf(btn); preload(thumbs[(btnIdx + 1) % thumbs.length]?.dataset.large); }; // デリゲーション(クリック) wrap.addEventListener('click', (e) => { const btn = e.target.closest('.thumb'); if (btn && wrap.contains(btn)) activate(btn); }); // ← → キーで移動(フォーカスがギャラリー内のとき) wrap.addEventListener('keydown', (e) => { if (!['ArrowLeft','ArrowRight'].includes(e.key)) return; const list = Array.from(thumbs); const current = wrap.querySelector('.thumb.is-active') || list[0]; const i = list.indexOf(current); const next = e.key === 'ArrowRight' ? list[(i + 1) % list.length] : list[(i - 1 + list.length) % list.length]; next.focus(); activate(next); }); // preload thumbs.forEach(t => preload(t.dataset.large)); }); });