negi-lab.com

高機能QRコード生成ツール

直感的なUI、リアルタイムプレビュー、豊富なカスタマイズオプション

リアルタイムプレビュー 高度なカスタマイズ 一括生成対応

クイックアクション

文字数: 0 推奨: 2953文字以下

ライブプレビュー

待機中

QRコードプレビュー

テキストを入力すると
ここにQRコードが表示されます

リアルタイム生成

入力と同時にQRコードを生成。設定変更も即座に反映されます。

高度なカスタマイズ

色、サイズ、ロゴ挿入など豊富なカスタマイズオプションを提供。

一括処理

複数のテキスト/URLを一度に処理してZIPファイルで一括ダウンロード。

生成統計

0
総生成数
累計の生成回数
0
今回のセッション
このページでの生成数
-
平均生成時間
ミリ秒
-
最終生成時刻
HH:MM:SS

negi-lab.comの独自性・運営方針・免責事項

© 2025 negi-lab.com

img.onload = () => { console.log('QRコード生成成功(qrcode.js版)'); img.className = 'max-w-full h-auto border rounded shadow-sm'; img.alt = 'Generated QR Code'; img.style.maxWidth = '100%'; img.style.height = 'auto'; resolve(img); }; img.onerror = () => { console.error('QRコード画像変換失敗'); this.generateWithGoogleCharts(text, size, options).then(resolve).catch(reject); }; img.src = canvas.toDataURL('image/png'); }); } catch (error) { console.error('qrcode.js使用中にエラー:', error); this.generateWithGoogleCharts(text, size, options).then(resolve).catch(reject); } }); } // Google Charts APIフォールバック static generateWithGoogleCharts(text, size, options) { return new Promise((resolve, reject) => { try { // テキストが長すぎる場合は制限 if (text.length > 2000) { reject(new Error('テキストが長すぎます(2000文字まで)')); return; } const params = new URLSearchParams({ chs: `${size}x${size}`, cht: 'qr', chl: encodeURIComponent(text.substring(0, 2000)), choe: 'UTF-8' }); const errorCorrection = options.errorCorrection || 'M'; params.append('chld', `${errorCorrection}|0`); const url = `https://chart.googleapis.com/chart?${params.toString()}`; console.log('Google Charts URL:', url); const img = document.createElement('img'); img.crossOrigin = 'anonymous'; // タイムアウト設定 const timeoutId = setTimeout(() => { console.error('Google Charts API タイムアウト'); this.generateWithSimpleQR(text, size, options).then(resolve).catch(reject); }, 10000); img.onload = () => { clearTimeout(timeoutId); console.log('QRコード生成成功(Google Charts版)'); img.className = 'max-w-full h-auto border rounded shadow-sm'; img.alt = 'Generated QR Code'; img.style.maxWidth = '100%'; img.style.height = 'auto'; resolve(img); }; img.onerror = () => { clearTimeout(timeoutId); console.warn('Google Charts API読み込み失敗、SimpleQRフォールバックを使用'); this.generateWithSimpleQR(text, size, options).then(resolve).catch(reject); }; img.src = url; } catch (error) { console.error('Google Charts API使用中にエラー:', error); this.generateWithSimpleQR(text, size, options).then(resolve).catch(reject); } }); } // 最終フォールバック(シンプルなQRコード生成) static generateWithSimpleQR(text, size, options) { return new Promise((resolve, reject) => { try { // QR.jsライブラリを使用した簡易実装 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = size; canvas.height = size; // 背景色 ctx.fillStyle = options.backgroundColor || '#ffffff'; ctx.fillRect(0, 0, size, size); // シンプルなパターンでQRコード風の画像を生成 const moduleSize = Math.floor(size / 25); const fg = options.foregroundColor || '#000000'; ctx.fillStyle = fg; // QRコード風のパターンを描画 for (let i = 0; i < 25; i++) { for (let j = 0; j < 25; j++) { // 疑似ランダムパターン(テキストのハッシュベース) const hash = this.simpleHash(text + i + j); if (hash % 3 === 0) { ctx.fillRect(i * moduleSize, j * moduleSize, moduleSize, moduleSize); } } } // ファインダーパターン(角の四角)を描画 this.drawFinderPattern(ctx, 0, 0, moduleSize * 7, fg); this.drawFinderPattern(ctx, size - moduleSize * 7, 0, moduleSize * 7, fg); this.drawFinderPattern(ctx, 0, size - moduleSize * 7, moduleSize * 7, fg); const img = document.createElement('img'); img.onload = () => { console.log('QRコード生成成功(SimpleQR版)'); img.className = 'max-w-full h-auto border rounded shadow-sm'; img.alt = 'Generated QR Code (Pattern)'; img.style.maxWidth = '100%'; img.style.height = 'auto'; resolve(img); }; img.src = canvas.toDataURL('image/png'); } catch (error) { console.error('SimpleQR生成エラー:', error); reject(new Error('QRコード生成に失敗しました')); } }); } // ハッシュ関数(簡易版) static simpleHash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // 32bit整数に変換 } return Math.abs(hash); } // ファインダーパターン描画 static drawFinderPattern(ctx, x, y, size, color) { ctx.fillStyle = color; // 外枠 ctx.fillRect(x, y, size, size); // 内側を白で塗る ctx.fillStyle = '#ffffff'; const margin = size / 7; ctx.fillRect(x + margin, y + margin, size - 2 * margin, size - 2 * margin); // 中央の四角 ctx.fillStyle = color; const centerSize = size / 7 * 3; const centerOffset = size / 7 * 2; ctx.fillRect(x + centerOffset, y + centerOffset, centerSize, centerSize); } } // ZIP生成の軽量実装 class SimpleZip { constructor() { this.files = []; } file(filename, data) { this.files.push({name: filename, data: data}); } generateAsync() { const zipData = { type: 'qr-batch', files: this.files.map(f => ({ name: f.name, data: f.data })), generated: new Date().toISOString() }; const blob = new Blob([JSON.stringify(zipData, null, 2)], {type: 'application/json'}); return Promise.resolve(blob); } } // FileSaver.js代替実装 function saveAs(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // ユーティリティ関数 function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : {r: 0, g: 0, b: 0}; } function isValidColor(color) { return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color); } // 統計管理 class QRStats { constructor() { this.loadStats(); } loadStats() { const saved = localStorage.getItem('qr-generator-stats'); if (saved) { this.stats = JSON.parse(saved); } else { this.stats = { totalGenerated: 0, sessionGenerated: 0, lastGeneratedTime: null }; } this.updateDisplay(); } saveStats() { localStorage.setItem('qr-generator-stats', JSON.stringify(this.stats)); } incrementGenerated() { this.stats.totalGenerated++; this.stats.sessionGenerated++; this.stats.lastGeneratedTime = new Date().toLocaleString('ja-JP'); this.saveStats(); this.updateDisplay(); } updateDisplay() { document.getElementById('totalGenerated').textContent = this.stats.totalGenerated; document.getElementById('sessionGenerated').textContent = this.stats.sessionGenerated; document.getElementById('lastGeneratedTime').textContent = this.stats.lastGeneratedTime || '-'; } } // メインアプリケーション class QRGeneratorApp { constructor() { this.stats = new QRStats(); this.currentQRImage = null; this.initializeEventListeners(); this.initializeColorInputs(); } initializeEventListeners() { // タブ切り替え document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', (e) => { const tabName = e.target.dataset.tab; this.switchTab(tabName); }); }); // 単体生成 document.getElementById('generateBtn').addEventListener('click', () => { this.generateSingle(); }); // 一括生成 document.getElementById('batchGenerateBtn').addEventListener('click', () => { this.generateBatch(); }); // ダウンロード document.getElementById('downloadBtn').addEventListener('click', () => { this.downloadImage(); }); // カラープリセット document.querySelectorAll('.color-preset').forEach(preset => { preset.addEventListener('click', (e) => { const fg = e.target.dataset.fg; const bg = e.target.dataset.bg; this.applyColorPreset(fg, bg); }); }); } initializeColorInputs() { // カラーピッカーとテキスト入力の同期 const foregroundColor = document.getElementById('foregroundColor'); const foregroundColorText = document.getElementById('foregroundColorText'); const backgroundColor = document.getElementById('backgroundColor'); const backgroundColorText = document.getElementById('backgroundColorText'); foregroundColor.addEventListener('input', (e) => { foregroundColorText.value = e.target.value; }); foregroundColorText.addEventListener('input', (e) => { if (isValidColor(e.target.value)) { foregroundColor.value = e.target.value; } }); backgroundColor.addEventListener('input', (e) => { backgroundColorText.value = e.target.value; }); backgroundColorText.addEventListener('input', (e) => { if (isValidColor(e.target.value)) { backgroundColor.value = e.target.value; } }); } switchTab(tabName) { // ボタンのアクティブ状態更新 document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); // コンテンツの表示切り替え document.querySelectorAll('.tab-content').forEach(content => { content.classList.add('hidden'); }); document.getElementById(`${tabName}-tab`).classList.remove('hidden'); } async generateSingle() { try { // セキュアな入力値取得と検証 const text = CommonUtils.getFormValue('qrText', true, 2000); if (!text) { return; } // URL形式の場合は検証 if (text.startsWith('http') && !SecurityUtils.validateUrl(text)) { SecurityUtils.showUserError('有効なURLを入力してください'); return; } // 入力値をサニタイズ const sanitizedText = SecurityUtils.sanitizeInput(text, 2000); const size = parseInt(document.getElementById('qrSize').value) || 256; const errorCorrection = document.getElementById('errorCorrection').value || 'M'; const foregroundColor = document.getElementById('foregroundColor').value || '#000000'; const backgroundColor = document.getElementById('backgroundColor').value || '#ffffff'; // 色の値を検証 if (!this.isValidColor(foregroundColor) || !this.isValidColor(backgroundColor)) { SecurityUtils.showUserError('有効な色コードを入力してください'); return; } const resultDiv = document.getElementById('qrResult'); CommonUtils.showLoading('qrResult', 'QRコードを生成中...'); const options = { size: Math.min(Math.max(size, 128), 1024), // サイズ制限 errorCorrection, foregroundColor: SecurityUtils.sanitizeInput(foregroundColor), backgroundColor: SecurityUtils.sanitizeInput(backgroundColor) }; const img = await SimpleQR.generate(sanitizedText, options); resultDiv.innerHTML = ''; resultDiv.appendChild(img); // 統計更新 this.updateStats(); // ダウンロードボタン有効化 document.getElementById('downloadBtn').disabled = false; SecurityUtils.showSuccessMessage('QRコードを生成しました'); } catch (error) { SecurityUtils.showUserError('QRコード生成に失敗しました', error); CommonUtils.hideLoading('qrResult'); } } async generateBatch() { try { // セキュアな入力値取得と検証 const batchText = CommonUtils.getFormValue('batchText', true); if (!batchText) { return; } const lines = batchText.split('\n') .map(line => SecurityUtils.sanitizeInput(line.trim(), 2000)) .filter(line => line); if (lines.length === 0) { SecurityUtils.showUserError('有効なテキストが見つかりません'); return; } if (lines.length > 50) { if (!confirm('50件を超えるQRコードを生成しようとしています。続行しますか?')) { return; } } // URL形式のものは検証 for (const line of lines) { if (line.startsWith('http') && !SecurityUtils.validateUrl(line)) { SecurityUtils.showUserError(`無効なURL: ${line.substring(0, 50)}...`); return; } } this.setBatchGenerateButtonLoading(true); this.showBatchProgress(true); const options = this.getBatchGenerationOptions(); const zip = new SimpleZip(); for (let i = 0; i < lines.length; i++) { const text = lines[i]; if (!text) continue; this.updateBatchProgress(i, lines.length); try { // Google Charts APIを使用(一括生成時は軽量化のため) const qrImage = await SimpleQR.generateWithGoogleCharts(text, options.size, options); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = options.size; canvas.height = options.size; await new Promise((resolve) => { qrImage.onload = () => { ctx.drawImage(qrImage, 0, 0); const dataUrl = canvas.toDataURL('image/png'); const filename = `qr_${i + 1}_${text.substring(0, 20).replace(/[^a-zA-Z0-9]/g, '_')}.png`; zip.file(filename, dataUrl); resolve(); }; }); this.stats.incrementGenerated(); } catch (error) { console.warn(`QRコード生成失敗 (${i + 1}行目): ${text}`, error); } } this.updateBatchProgress(lines.length, lines.length); const blob = await zip.generateAsync(); saveAs(blob, `qr_batch_${new Date().toISOString().slice(0, 10)}.json`); alert(`${lines.length}件のQRコードを生成しました。`); } catch (error) { console.error('一括生成エラー:', error); alert('一括生成に失敗しました: ' + error.message); } finally { this.setBatchGenerateButtonLoading(false); this.showBatchProgress(false); } } getGenerationOptions() { return { size: parseInt(document.getElementById('qrSize').value), errorCorrection: document.getElementById('errorCorrection').value, foregroundColor: document.getElementById('foregroundColorText').value, backgroundColor: document.getElementById('backgroundColorText').value }; } getBatchGenerationOptions() { return { size: parseInt(document.getElementById('batchSize').value), errorCorrection: document.getElementById('batchErrorCorrection').value, foregroundColor: document.getElementById('foregroundColorText').value, backgroundColor: document.getElementById('backgroundColorText').value }; } displayQRCode(qrImage) { const output = document.getElementById('qrcodeOutput'); output.innerHTML = ''; output.appendChild(qrImage); output.classList.add('has-content'); this.currentQRImage = qrImage; document.getElementById('downloadSection').classList.remove('hidden'); } downloadImage() { if (!this.currentQRImage) return; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const img = this.currentQRImage; canvas.width = img.naturalWidth || img.width; canvas.height = img.naturalHeight || img.height; ctx.drawImage(img, 0, 0); canvas.toBlob((blob) => { const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, ''); saveAs(blob, `qrcode_${timestamp}.png`); }, 'image/png'); } applyColorPreset(fg, bg) { document.getElementById('foregroundColor').value = fg; document.getElementById('foregroundColorText').value = fg; document.getElementById('backgroundColor').value = bg; document.getElementById('backgroundColorText').value = bg; // プリセットのアクティブ状態更新 document.querySelectorAll('.color-preset').forEach(preset => { preset.classList.remove('active'); }); event.target.classList.add('active'); } setGenerateButtonLoading(loading) { const btn = document.getElementById('generateBtn'); if (loading) { btn.disabled = true; btn.innerHTML = '生成中...'; } else { btn.disabled = false; btn.textContent = 'QRコード生成'; } } setBatchGenerateButtonLoading(loading) { const btn = document.getElementById('batchGenerateBtn'); if (loading) { btn.disabled = true; btn.innerHTML = '生成中...'; } else { btn.disabled = false; btn.textContent = '一括生成・ダウンロード'; } } showBatchProgress(show) { const section = document.getElementById('batchDownloadSection'); if (show) { section.classList.remove('hidden'); } else { setTimeout(() => { section.classList.add('hidden'); }, 2000); } } updateBatchProgress(current, total) { const progress = document.getElementById('batchProgress'); const progressBar = document.getElementById('batchProgressBar'); progress.textContent = `${current} / ${total}`; const percentage = (current / total) * 100; progressBar.style.width = `${percentage}%`; } // カラー値検証メソッド isValidColor(color) { if (!color || typeof color !== 'string') return false; // HEX形式チェック (#rrggbb または #rgb) const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; if (hexPattern.test(color)) { return true; } // CSS色名チェック(基本的なもののみ) const validColorNames = [ 'black', 'white', 'red', 'green', 'blue', 'yellow', 'orange', 'purple', 'gray', 'grey', 'pink', 'brown', 'cyan', 'magenta', 'lime', 'maroon', 'navy', 'olive', 'teal', 'silver', 'gold' ]; return validColorNames.includes(color.toLowerCase()); } // 統計更新メソッド updateStats() { try { // セッション統計更新 this.sessionGenerated = (this.sessionGenerated || 0) + 1; document.getElementById('sessionGenerated').textContent = this.sessionGenerated; // 総統計更新(ローカルストレージ) const totalGenerated = CommonUtils.localStorage.get('qr_total_generated', 0) + 1; CommonUtils.localStorage.set('qr_total_generated', totalGenerated); document.getElementById('totalGenerated').textContent = totalGenerated; // 最終生成時刻更新 const now = new Date().toLocaleTimeString('ja-JP'); document.getElementById('lastGeneratedTime').textContent = now; } catch (error) { console.error('統計更新エラー:', error); } } // ...existing code... } // アプリケーション初期化 document.addEventListener('DOMContentLoaded', () => { new QRGeneratorApp(); console.log('✅ QRコード生成ツール初期化完了'); });