287 lines
10 KiB
JavaScript
287 lines
10 KiB
JavaScript
|
|
import { loadCssFile } from "./utils.js";
|
|
|
|
const CSS_FILEPATH = "/D2/assets/css/D2_Lightbox.css";
|
|
|
|
loadCssFile(CSS_FILEPATH);
|
|
|
|
/**
|
|
* エレメント作成
|
|
*/
|
|
/**
|
|
* ライトボックスの基本要素を保持する静的プロパティ
|
|
*/
|
|
let lightboxElements = null;
|
|
let nowIndex = 0;
|
|
let images = [];
|
|
let resizeTimer;
|
|
|
|
/**
|
|
* エレメント作成と初期化
|
|
*/
|
|
const _initLightboxBase = () => {
|
|
if (lightboxElements) {
|
|
return lightboxElements; // 既に初期化済みなら既存の要素を返す
|
|
}
|
|
|
|
const container = document.createElement('div');
|
|
container.classList.add('d2-lightbox');
|
|
container.style.display = 'none'; // 初期状態は非表示
|
|
// フォーカス可能にする
|
|
container.setAttribute('tabindex', '-1'); // フォーカス可能にする
|
|
document.body.appendChild(container);
|
|
|
|
const bg = document.createElement('div');
|
|
bg.classList.add('d2-lightbox__bg');
|
|
container.appendChild(bg);
|
|
|
|
const content = document.createElement('div');
|
|
content.classList.add('d2-lightbox__content');
|
|
container.appendChild(content);
|
|
|
|
const galleryContainer = document.createElement('div');
|
|
galleryContainer.classList.add('d2-lightbox__gallery');
|
|
content.appendChild(galleryContainer);
|
|
galleryContainer.addEventListener('click', D2Lightbox.closeLightbox); // クリックで閉じる
|
|
|
|
const galleryImage = document.createElement('img');
|
|
galleryImage.classList.add('d2-lightbox__gallery-image');
|
|
galleryImage.style.display = 'none'; // 初期状態は非表示
|
|
galleryContainer.appendChild(galleryImage);
|
|
|
|
const listContainer = document.createElement('div');
|
|
listContainer.classList.add('d2-lightbox__list');
|
|
content.appendChild(listContainer);
|
|
|
|
// 生成した要素を静的プロパティに格納
|
|
lightboxElements = {
|
|
container, galleryContainer, galleryImage, listContainer
|
|
};
|
|
return lightboxElements;
|
|
}
|
|
|
|
|
|
/**
|
|
* 静的メソッドのみを持つライトボックスクラス
|
|
*/
|
|
class D2Lightbox {
|
|
|
|
// constructor は不要
|
|
|
|
static openLightbox(imageUrls, imageIndex = 0) {
|
|
// 初回呼び出し時に要素を初期化
|
|
if (!lightboxElements) {
|
|
_initLightboxBase();
|
|
}
|
|
|
|
D2Lightbox._removeListImages();
|
|
D2Lightbox._addListimageObjects(imageUrls);
|
|
D2Lightbox._clickListImage(imageIndex);
|
|
lightboxElements.container.style.display = 'block';
|
|
|
|
// Lightboxを開いた時に自動でフォーカスを設定
|
|
lightboxElements.container.focus();
|
|
|
|
// リサイズイベントリスナーを追加(重複防止のため削除してから追加)
|
|
window.removeEventListener('resize', D2Lightbox._handleResize);
|
|
window.addEventListener('resize', D2Lightbox._handleResize);
|
|
// キーボードイベントリスナーを追加(重複防止のため削除してから追加)
|
|
document.removeEventListener('keydown', D2Lightbox._onKeyDown);
|
|
document.addEventListener('keydown', D2Lightbox._onKeyDown);
|
|
}
|
|
|
|
static closeLightbox() {
|
|
if (!lightboxElements) return; // 要素がなければ何もしない
|
|
|
|
lightboxElements.container.style.display = 'none';
|
|
lightboxElements.galleryImage.style.display = 'none';
|
|
D2Lightbox._removeListImages();
|
|
|
|
// イベントリスナーを削除
|
|
window.removeEventListener('resize', D2Lightbox._handleResize);
|
|
document.removeEventListener('keydown', D2Lightbox._onKeyDown);
|
|
}
|
|
|
|
/**
|
|
* リスト画像をクリック
|
|
* @param {int} index
|
|
*/
|
|
static _clickListImage(index) {
|
|
if (!lightboxElements || !images || images.length === 0) return;
|
|
|
|
// 前の画像を非アクティブに
|
|
if (images[nowIndex]) {
|
|
images[nowIndex].dataset.active = 'false';
|
|
}
|
|
|
|
const targetImg = images[index];
|
|
if (!targetImg) return; // 対象画像がなければ終了
|
|
|
|
targetImg.dataset.active = 'true';
|
|
lightboxElements.galleryImage.src = targetImg.src;
|
|
lightboxElements.galleryImage.style.display = 'inline';
|
|
lightboxElements.galleryImage.style.width = 'auto';
|
|
lightboxElements.galleryImage.style.height = 'auto';
|
|
nowIndex = index;
|
|
|
|
// 画像読み込み後にリサイズ処理
|
|
lightboxElements.galleryImage.decode().then(() => {
|
|
D2Lightbox._resetGalleryPosition();
|
|
}).catch(error => {
|
|
console.error("Error decoding image:", error);
|
|
// エラー時もリサイズを試みる(表示されているかもしれないため)
|
|
D2Lightbox._resetGalleryPosition();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 画像サイズと位置の調整
|
|
*/
|
|
static _resetGalleryPosition() {
|
|
if (!lightboxElements || !lightboxElements.galleryImage.src || lightboxElements.galleryImage.style.display === 'none') return;
|
|
|
|
// コンテナサイズ取得。高さはリストを除外した高さ
|
|
const containerW = lightboxElements.galleryContainer.clientWidth;
|
|
// リストの高さを取得(存在しない場合や非表示の場合は0)
|
|
const listH = lightboxElements.listContainer.offsetHeight || 0;
|
|
const containerH = lightboxElements.galleryContainer.clientHeight - listH; // リストの高さを引く
|
|
|
|
// 画像の自然なサイズを取得
|
|
const imgW = lightboxElements.galleryImage.naturalWidth;
|
|
const imgH = lightboxElements.galleryImage.naturalHeight;
|
|
|
|
if (containerW <= 0 || containerH <= 0 || imgW <= 0 || imgH <= 0) {
|
|
// 無効なサイズの場合は何もしない
|
|
return;
|
|
}
|
|
|
|
const aspect = imgW / imgH;
|
|
let newWidth, newHeight;
|
|
|
|
// コンテナに合わせてサイズを計算
|
|
if (containerW / containerH > aspect) {
|
|
// コンテナの方が横長の場合、高さに合わせる
|
|
newHeight = Math.min(imgH, containerH); // 元画像より大きくしない
|
|
newWidth = newHeight * aspect;
|
|
if (newWidth > containerW) { // 幅がはみ出る場合は幅に合わせる
|
|
newWidth = containerW;
|
|
newHeight = newWidth / aspect;
|
|
}
|
|
} else {
|
|
// コンテナの方が縦長またはアスペクト比が同じ場合、幅に合わせる
|
|
newWidth = Math.min(imgW, containerW); // 元画像より大きくしない
|
|
newHeight = newWidth / aspect;
|
|
if (newHeight > containerH) { // 高さがはみ出る場合は高さに合わせる
|
|
newHeight = containerH;
|
|
newWidth = newHeight * aspect;
|
|
}
|
|
}
|
|
|
|
// 画像の新しいサイズを設定
|
|
lightboxElements.galleryImage.style.width = `${newWidth}px`;
|
|
lightboxElements.galleryImage.style.height = `${newHeight}px`;
|
|
|
|
// 中央配置のための位置計算
|
|
const left = (containerW - newWidth) / 2;
|
|
// topの位置はリストの高さを考慮しないgalleryContainer基準で計算
|
|
const top = (containerH - newHeight) / 2;
|
|
|
|
// 位置を設定
|
|
lightboxElements.galleryImage.style.left = `${left}px`;
|
|
// topはリストの高さを考慮しない位置
|
|
lightboxElements.galleryImage.style.top = `${top}px`;
|
|
}
|
|
|
|
|
|
/**
|
|
* リスト画像を追加
|
|
* @param {Array<string>} imageUrls
|
|
*/
|
|
static _addListimageObjects(imageUrls) {
|
|
if (!lightboxElements) return;
|
|
images = []; // リストをクリア
|
|
lightboxElements.listContainer.innerHTML = ''; // コンテナをクリア
|
|
|
|
imageUrls.forEach((url, index) => {
|
|
// let url = IMAGE_URL.replace('{filename}', imgObj.filename);
|
|
// url = url.replace('{random}', Math.random());
|
|
|
|
const img = document.createElement('img');
|
|
img.classList.add('d2-lightbox__list-image');
|
|
img.src = url;
|
|
// img.index = index; // datasetで管理する方が安全
|
|
img.dataset.index = index;
|
|
img.dataset.active = 'false';
|
|
img.addEventListener('click', (ev) => {
|
|
// クリックされた画像のインデックスを取得
|
|
const clickedIndex = parseInt(ev.target.dataset.index, 10);
|
|
D2Lightbox._clickListImage(clickedIndex);
|
|
});
|
|
lightboxElements.listContainer.appendChild(img);
|
|
images.push(img); // 静的プロパティの配列に追加
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 既存のリスト画像を削除する
|
|
*/
|
|
static _removeListImages() {
|
|
if (!lightboxElements) return;
|
|
images.forEach(img => {
|
|
if (img.parentNode === lightboxElements.listContainer) {
|
|
lightboxElements.listContainer.removeChild(img);
|
|
}
|
|
});
|
|
images = []; // 静的プロパティの配列をクリア
|
|
nowIndex = 0; // インデックスもリセット
|
|
}
|
|
|
|
// デバウンス処理付きのリサイズハンドラ
|
|
static _handleResize = () => {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(() => {
|
|
D2Lightbox._resetGalleryPosition();
|
|
}, 200); // 200ms後に実行
|
|
};
|
|
|
|
/**
|
|
* ライトボックスが表示されているかどうかを返す
|
|
* @returns {boolean}
|
|
*/
|
|
static _isLightboxVisible() {
|
|
return lightboxElements && lightboxElements.container.style.display !== 'none';
|
|
}
|
|
|
|
// キーダウンイベントハンドラ
|
|
static _onKeyDown = (ev) => {
|
|
// ライトボックスが表示されていない場合は何もしない
|
|
if (!D2Lightbox._isLightboxVisible()) {
|
|
return;
|
|
}
|
|
|
|
let handled = false;
|
|
if (ev.key === 'Escape') {
|
|
D2Lightbox.closeLightbox();
|
|
handled = true;
|
|
} else if (ev.key === 'ArrowLeft') {
|
|
// 左矢印キー: 前の画像へ
|
|
const prevIndex = (nowIndex - 1 + images.length) % images.length;
|
|
D2Lightbox._clickListImage(prevIndex);
|
|
handled = true;
|
|
} else if (ev.key === 'ArrowRight') {
|
|
// 右矢印キー: 次の画像へ
|
|
const nextIndex = (nowIndex + 1) % images.length;
|
|
D2Lightbox._clickListImage(nextIndex);
|
|
handled = true;
|
|
}
|
|
|
|
// ライトボックスでキーが処理された場合、イベントの伝播とデフォルト動作を停止
|
|
if (handled) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
}
|
|
}
|
|
|
|
export { D2Lightbox };
|