/** * AspectCalc サンプルページのメインスクリプト */ class AspectCalcApp { constructor() { this.initializeElements(); this.bindEvents(); this.setupClipboardPaste(); } /** * DOM要素の初期化 */ initializeElements() { this.dropZone = document.getElementById('dropZone'); this.fileInput = document.getElementById('fileInput'); this.fileSelectBtn = document.getElementById('fileSelectBtn'); this.pasteBtn = document.getElementById('pasteBtn'); this.widthInput = document.getElementById('widthInput'); this.heightInput = document.getElementById('heightInput'); this.calculateBtn = document.getElementById('calculateBtn'); this.previewArea = document.getElementById('previewArea'); this.resolutionInfo = document.getElementById('resolutionInfo'); this.actualWidth = document.getElementById('actualWidth'); this.actualHeight = document.getElementById('actualHeight'); this.resultsArea = document.getElementById('resultsArea'); this.integerRatio = document.getElementById('integerRatio'); this.approximateRatio = document.getElementById('approximateRatio'); this.standardRatio = document.getElementById('standardRatio'); this.toast = document.getElementById('toast'); this.toastMessage = document.getElementById('toastMessage'); } /** * イベントリスナーの設定 */ bindEvents() { // ファイル選択 this.fileSelectBtn.addEventListener('click', () => { this.fileInput.click(); }); this.fileInput.addEventListener('change', (e) => { this.handleFileSelect(e.target.files[0]); }); // ドラッグ&ドロップ this.dropZone.addEventListener('dragover', (e) => { e.preventDefault(); this.dropZone.classList.add('dragover'); }); this.dropZone.addEventListener('dragleave', () => { this.dropZone.classList.remove('dragover'); }); this.dropZone.addEventListener('drop', (e) => { e.preventDefault(); this.dropZone.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0) { this.handleFileSelect(files[0]); } }); // 手動計算 this.calculateBtn.addEventListener('click', () => { this.handleManualInput(); }); // Enterキーでの計算 this.widthInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.handleManualInput(); } }); this.heightInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.handleManualInput(); } }); // コピーボタン document.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', (e) => { const ratioType = e.target.getAttribute('data-ratio'); this.copyRatio(ratioType); }); }); } /** * クリップボード貼り付け機能の設定 */ setupClipboardPaste() { // キーボードイベント(Ctrl+V) document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'v') { e.preventDefault(); this.handleClipboardPaste(); } }); // 貼り付けボタン this.pasteBtn.addEventListener('click', () => { this.handleClipboardPaste(); }); } /** * ファイル選択の処理 */ handleFileSelect(file) { if (!file || !file.type.startsWith('image/')) { this.showToast('有効な画像ファイルを選択してください', 'error'); return; } const reader = new FileReader(); reader.onload = (e) => { this.processImage(e.target.result, file.name); }; reader.readAsDataURL(file); } /** * クリップボード貼り付けの処理 */ async handleClipboardPaste() { try { const clipboardItems = await navigator.clipboard.read(); for (const clipboardItem of clipboardItems) { for (const type of clipboardItem.types) { if (type.startsWith('image/')) { const blob = await clipboardItem.getType(type); const reader = new FileReader(); reader.onload = (e) => { this.processImage(e.target.result, 'クリップボード画像'); }; reader.readAsDataURL(blob); return; } } } this.showToast('クリップボードに画像が見つかりません', 'error'); } catch (error) { console.error('クリップボード読み取りエラー:', error); this.showToast('クリップボードの読み取りに失敗しました', 'error'); } } /** * 手動入力の処理 */ handleManualInput() { const width = parseInt(this.widthInput.value); const height = parseInt(this.heightInput.value); if (!width || !height || width <= 0 || height <= 0) { this.showToast('有効な数値を入力してください', 'error'); return; } this.calculateAspectRatios(width, height); this.showResults(); } /** * 画像の処理 */ processImage(imageSrc, fileName) { const img = new Image(); img.onload = () => { // プレビュー表示 this.showImagePreview(imageSrc, fileName); // 解像度情報表示 this.showResolutionInfo(img.width, img.height); // アスペクト比計算 this.calculateAspectRatios(img.width, img.height); // 結果表示 this.showResults(); }; img.onerror = () => { this.showToast('画像の読み込みに失敗しました', 'error'); }; img.src = imageSrc; } /** * 画像プレビューの表示 */ showImagePreview(imageSrc, fileName) { this.previewArea.innerHTML = `
プレビュー
${fileName} `; } /** * 解像度情報の表示 */ showResolutionInfo(width, height) { this.actualWidth.textContent = width.toLocaleString(); this.actualHeight.textContent = height.toLocaleString(); this.resolutionInfo.classList.remove('d-none'); } /** * アスペクト比の計算 */ calculateAspectRatios(width, height) { try { const results = AspectCalc.calculate(width, height); // 結果の保存(コピー機能用) this.currentResults = results; // 表示更新 this.integerRatio.textContent = `${results.integer.x}:${results.integer.y}`; this.approximateRatio.textContent = `${results.approximate.x}:${results.approximate.y}`; this.standardRatio.textContent = `${results.standard.x}:${results.standard.y}`; } catch (error) { console.error('アスペクト比計算エラー:', error); this.showToast('アスペクト比の計算に失敗しました', 'error'); } } /** * 結果エリアの表示 */ showResults() { this.resultsArea.classList.remove('d-none'); this.resultsArea.scrollIntoView({ behavior: 'smooth' }); } /** * アスペクト比のコピー */ copyRatio(ratioType) { if (!this.currentResults) { this.showToast('コピーするデータがありません', 'error'); return; } const ratio = this.currentResults[ratioType]; const text = `${ratio.x}:${ratio.y}`; navigator.clipboard.writeText(text).then(() => { this.showToast(`「${text}」をコピーしました!`); }).catch(() => { this.showToast('コピーに失敗しました', 'error'); }); } /** * トースト通知の表示 */ showToast(message, type = 'success') { this.toastMessage.textContent = message; // アイコンの変更 const icon = this.toast.querySelector('i'); if (type === 'error') { icon.className = 'fas fa-exclamation-circle text-danger me-2'; } else { icon.className = 'fas fa-check-circle text-success me-2'; } // トーストの表示 const toast = new bootstrap.Toast(this.toast); toast.show(); } } // アプリケーションの初期化 document.addEventListener('DOMContentLoaded', () => { new AspectCalcApp(); });