AspectCalc / index.js
SenY's picture
Init.
3c13b11
/**
* 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 = `
<div class="mb-2">
<img src="${imageSrc}" alt="プレビュー" class="image-preview">
</div>
<small class="text-muted">${fileName}</small>
`;
}
/**
* 解像度情報の表示
*/
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();
});