- Updated barcode.html script reference to use local Quagga2 library - Quagga2 provides improved barcode detection accuracy and performance - API is backward compatible with original Quagga.js, no code changes needed - Added technical notes to README documenting library location and setup - Library location: ./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js (153 KB) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
616 lines
19 KiB
HTML
616 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="stylesheet" href="main.css">
|
|
<title>Barcode Scanner</title>
|
|
<script src="./quagga2/quagga2-1.12.1/docs/examples/dist/quagga.min.js"></script>
|
|
<style>
|
|
.scanner-container {
|
|
background: #f9f9f9;
|
|
padding: 30px;
|
|
border-radius: 8px;
|
|
max-width: 800px;
|
|
margin: 30px auto;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.scanner-header {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.scanner-header h3 {
|
|
color: #333;
|
|
margin-top: 0;
|
|
}
|
|
|
|
.scanner-header p {
|
|
color: #666;
|
|
font-size: 14px;
|
|
margin: 10px 0 0 0;
|
|
}
|
|
|
|
.mode-toggle {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.mode-btn {
|
|
padding: 10px 20px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 4px;
|
|
background: white;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.mode-btn.active {
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
border-color: #4CAF50;
|
|
}
|
|
|
|
.mode-btn:hover:not(.active) {
|
|
border-color: #4CAF50;
|
|
}
|
|
|
|
.camera-section {
|
|
display: none;
|
|
}
|
|
|
|
.camera-section.active {
|
|
display: block;
|
|
}
|
|
|
|
.keyboard-section {
|
|
display: block;
|
|
}
|
|
|
|
.keyboard-section.hidden {
|
|
display: none;
|
|
}
|
|
|
|
#video {
|
|
width: 100%;
|
|
border-radius: 4px;
|
|
background: #000;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.camera-controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 15px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.barcode-input-wrapper {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.barcode-input-wrapper label {
|
|
display: block;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.barcode-input {
|
|
width: 100%;
|
|
padding: 15px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 18px;
|
|
font-family: 'Courier New', monospace;
|
|
box-sizing: border-box;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.barcode-input:focus {
|
|
outline: none;
|
|
border-color: #4CAF50;
|
|
box-shadow: 0 0 8px rgba(76, 175, 80, 0.3);
|
|
background-color: #fffef0;
|
|
}
|
|
|
|
.barcode-input.highlight {
|
|
border-color: #4CAF50;
|
|
background-color: #e8f5e9;
|
|
}
|
|
|
|
.scanner-status {
|
|
text-align: center;
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
background-color: #e8f5e9;
|
|
border: 1px solid #c8e6c9;
|
|
color: #2e7d32;
|
|
font-size: 14px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.scanner-status.active {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
background-color: #e8f5e9;
|
|
}
|
|
50% {
|
|
background-color: #c8e6c9;
|
|
}
|
|
}
|
|
|
|
.scanner-info {
|
|
background-color: #e3f2fd;
|
|
border: 1px solid #bbdefb;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-top: 20px;
|
|
font-size: 13px;
|
|
color: #1565c0;
|
|
}
|
|
|
|
.scanner-info h4 {
|
|
margin: 0 0 10px 0;
|
|
color: #0d47a1;
|
|
}
|
|
|
|
.scanner-info ul {
|
|
margin: 10px 0 0 0;
|
|
padding-left: 20px;
|
|
}
|
|
|
|
.scanner-info li {
|
|
margin: 5px 0;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.console-hint {
|
|
background-color: #fff3e0;
|
|
border: 1px solid #ffe0b2;
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
margin-top: 15px;
|
|
font-size: 13px;
|
|
color: #e65100;
|
|
}
|
|
|
|
.console-hint strong {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
color: #d84315;
|
|
}
|
|
|
|
.button-group {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn {
|
|
padding: 12px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
flex: 1;
|
|
min-width: 120px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: #45a049;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: #ddd;
|
|
color: #333;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: #bbb;
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: #f44336;
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background-color: #da190b;
|
|
}
|
|
|
|
.open-console {
|
|
background-color: #2196F3;
|
|
color: white;
|
|
}
|
|
|
|
.open-console:hover {
|
|
background-color: #0b7dda;
|
|
}
|
|
|
|
.error-message {
|
|
background-color: #ffebee;
|
|
border: 1px solid #f5a5ac;
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
color: #c62828;
|
|
margin-bottom: 15px;
|
|
display: none;
|
|
}
|
|
|
|
.error-message.show {
|
|
display: block;
|
|
}
|
|
|
|
.detected-barcode {
|
|
background-color: #e8f5e9;
|
|
border: 2px solid #4CAF50;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
text-align: center;
|
|
margin-top: 15px;
|
|
display: none;
|
|
}
|
|
|
|
.detected-barcode.show {
|
|
display: block;
|
|
}
|
|
|
|
.detected-barcode-value {
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 20px;
|
|
font-weight: bold;
|
|
color: #2e7d32;
|
|
}
|
|
|
|
/* Responsive design */
|
|
@media (max-width: 600px) {
|
|
.scanner-container {
|
|
padding: 20px;
|
|
margin: 20px 10px;
|
|
}
|
|
|
|
.barcode-input {
|
|
font-size: 16px;
|
|
padding: 12px;
|
|
}
|
|
|
|
.button-group {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
}
|
|
|
|
.mode-toggle {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.mode-btn {
|
|
flex: 1;
|
|
min-width: 120px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="navbar-placeholder"></div>
|
|
<div id="page-content">
|
|
<h2>Barcode Scanner</h2>
|
|
<hr>
|
|
|
|
<div class="scanner-container">
|
|
<div class="scanner-header">
|
|
<h3>📱 Barcode Scanner</h3>
|
|
<p>Scan barcodes using camera or keyboard input</p>
|
|
</div>
|
|
|
|
<!-- Mode Toggle -->
|
|
<div class="mode-toggle">
|
|
<button type="button" class="mode-btn active" id="keyboard-mode-btn">
|
|
⌨️ Keyboard Input
|
|
</button>
|
|
<button type="button" class="mode-btn" id="camera-mode-btn">
|
|
📷 Camera Scan
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Error Message -->
|
|
<div class="error-message" id="error-message"></div>
|
|
|
|
<!-- Keyboard Input Section -->
|
|
<div class="keyboard-section" id="keyboard-section">
|
|
<div class="scanner-status active" id="scanner-status">
|
|
✓ Scanner Ready - Click below and scan a barcode
|
|
</div>
|
|
|
|
<div class="barcode-input-wrapper">
|
|
<label for="barcode-input">Barcode Input:</label>
|
|
<input
|
|
type="text"
|
|
id="barcode-input"
|
|
class="barcode-input"
|
|
placeholder="Scan or type barcode here..."
|
|
autocomplete="off"
|
|
spellcheck="false"
|
|
>
|
|
</div>
|
|
|
|
<div class="scanner-info">
|
|
<h4>How to Use (Keyboard):</h4>
|
|
<ul>
|
|
<li><strong>Keyboard Entry:</strong> Type or paste a barcode and press Enter</li>
|
|
<li><strong>Hardware Scanner:</strong> Plug in a barcode scanner and scan directly</li>
|
|
<li><strong>Testing:</strong> Scanned barcodes are logged to the browser console (Press F12)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Camera Section -->
|
|
<div class="camera-section" id="camera-section">
|
|
<div class="scanner-status" id="camera-status">
|
|
📷 Camera Ready - Click "Start Camera" to begin scanning
|
|
</div>
|
|
|
|
<video id="video" width="800" height="600"></video>
|
|
|
|
<div class="camera-controls">
|
|
<button type="button" class="btn btn-primary" id="start-camera-btn">
|
|
▶️ Start Camera
|
|
</button>
|
|
<button type="button" class="btn btn-danger" id="stop-camera-btn" style="display: none;">
|
|
⏹️ Stop Camera
|
|
</button>
|
|
</div>
|
|
|
|
<div id="detected-barcode-display" class="detected-barcode">
|
|
<div style="font-size: 12px; color: #666; margin-bottom: 5px;">Detected Barcode:</div>
|
|
<div class="detected-barcode-value" id="detected-barcode-value">-</div>
|
|
</div>
|
|
|
|
<div class="scanner-info">
|
|
<h4>How to Use (Camera):</h4>
|
|
<ul>
|
|
<li><strong>Permission:</strong> Browser will request camera access on first use</li>
|
|
<li><strong>Position:</strong> Hold barcode in front of camera lens</li>
|
|
<li><strong>Detection:</strong> Barcode is detected automatically in real-time</li>
|
|
<li><strong>Supported:</strong> UPC, EAN, Code-128, Code-39, and more</li>
|
|
<li><strong>Console:</strong> All detections logged to console (Press F12)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="console-hint">
|
|
<strong>🔍 View Console Output:</strong>
|
|
Press F12 (or Cmd+Option+I on Mac) to open Developer Tools. Check the Console tab to see scanned barcodes logged with timestamps and input type detection.
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
<button type="button" class="btn open-console" id="open-console-btn">
|
|
📟 Open Console (F12)
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="clear-input-btn">
|
|
🗑️ Clear Input
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="navbar.js"></script>
|
|
<script type="module">
|
|
import { initializeBarcodeScanner, logBarcodeToConsole } from './barcode-scanner.js';
|
|
|
|
const keyboardModeBtn = document.getElementById('keyboard-mode-btn');
|
|
const cameraModeBtn = document.getElementById('camera-mode-btn');
|
|
const keyboardSection = document.getElementById('keyboard-section');
|
|
const cameraSection = document.getElementById('camera-section');
|
|
const barcodeInput = document.getElementById('barcode-input');
|
|
const scannerStatus = document.getElementById('scanner-status');
|
|
const cameraStatus = document.getElementById('camera-status');
|
|
const video = document.getElementById('video');
|
|
const startCameraBtn = document.getElementById('start-camera-btn');
|
|
const stopCameraBtn = document.getElementById('stop-camera-btn');
|
|
const openConsoleBtn = document.getElementById('open-console-btn');
|
|
const clearInputBtn = document.getElementById('clear-input-btn');
|
|
const errorMessage = document.getElementById('error-message');
|
|
const detectedBarcodeDisplay = document.getElementById('detected-barcode-display');
|
|
const detectedBarcodeValue = document.getElementById('detected-barcode-value');
|
|
|
|
let cameraActive = false;
|
|
|
|
/**
|
|
* Callback when a barcode is scanned
|
|
*/
|
|
function onBarcodeScanned(barcode, inputType) {
|
|
if (inputType === 'camera') {
|
|
// Camera detection feedback
|
|
detectedBarcodeValue.textContent = barcode;
|
|
detectedBarcodeDisplay.classList.add('show');
|
|
cameraStatus.textContent = `✓ Barcode detected: ${barcode}`;
|
|
cameraStatus.style.backgroundColor = '#a5d6a7';
|
|
setTimeout(() => {
|
|
cameraStatus.textContent = '📷 Scanning...';
|
|
cameraStatus.style.backgroundColor = '';
|
|
detectedBarcodeDisplay.classList.remove('show');
|
|
}, 2000);
|
|
} else {
|
|
// Keyboard detection feedback
|
|
const originalStatus = scannerStatus.textContent;
|
|
scannerStatus.textContent = `✓ Barcode scanned: ${barcode}`;
|
|
scannerStatus.style.backgroundColor = '#a5d6a7';
|
|
setTimeout(() => {
|
|
scannerStatus.textContent = originalStatus;
|
|
scannerStatus.style.backgroundColor = '';
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Switch to keyboard mode
|
|
*/
|
|
function switchToKeyboardMode() {
|
|
keyboardModeBtn.classList.add('active');
|
|
cameraModeBtn.classList.remove('active');
|
|
keyboardSection.classList.remove('hidden');
|
|
cameraSection.classList.remove('active');
|
|
|
|
if (cameraActive) {
|
|
stopCamera();
|
|
}
|
|
|
|
barcodeInput.focus();
|
|
}
|
|
|
|
/**
|
|
* Switch to camera mode
|
|
*/
|
|
function switchToCameraMode() {
|
|
cameraModeBtn.classList.add('active');
|
|
keyboardModeBtn.classList.remove('active');
|
|
cameraSection.classList.add('active');
|
|
keyboardSection.classList.add('hidden');
|
|
errorMessage.classList.remove('show');
|
|
}
|
|
|
|
/**
|
|
* Start camera and barcode detection
|
|
*/
|
|
async function startCamera() {
|
|
try {
|
|
cameraActive = true;
|
|
startCameraBtn.style.display = 'none';
|
|
stopCameraBtn.style.display = 'block';
|
|
cameraStatus.textContent = '📷 Scanning...';
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
video: { facingMode: 'environment', width: { ideal: 800 }, height: { ideal: 600 } }
|
|
});
|
|
|
|
video.srcObject = stream;
|
|
video.setAttribute('playsinline', 'true');
|
|
|
|
// Wait for video to load
|
|
await new Promise(resolve => {
|
|
video.onloadedmetadata = resolve;
|
|
});
|
|
|
|
video.play();
|
|
|
|
// Initialize Quagga barcode detection
|
|
Quagga.init({
|
|
inputStream: {
|
|
name: 'Live',
|
|
type: 'LiveStream',
|
|
target: '#video',
|
|
constraints: { width: 800, height: 600, facingMode: 'environment' }
|
|
},
|
|
decoder: {
|
|
readers: ['ean_reader', 'ean_8_reader', 'upc_reader', 'upc_e_reader', 'code_128_reader', 'code_39_reader']
|
|
}
|
|
}, function(err) {
|
|
if (err) {
|
|
showError('Camera initialization error: ' + err.message);
|
|
stopCamera();
|
|
return;
|
|
}
|
|
Quagga.start();
|
|
});
|
|
|
|
// Handle barcode detection
|
|
Quagga.onDetected(function(result) {
|
|
if (result.codeResult) {
|
|
const barcode = result.codeResult.code;
|
|
logBarcodeToConsole(barcode, 'camera');
|
|
onBarcodeScanned(barcode, 'camera');
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
showError('Camera access denied or not available: ' + error.message);
|
|
stopCamera();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop camera and barcode detection
|
|
*/
|
|
function stopCamera() {
|
|
cameraActive = false;
|
|
startCameraBtn.style.display = 'block';
|
|
stopCameraBtn.style.display = 'none';
|
|
cameraStatus.textContent = '📷 Camera stopped';
|
|
|
|
if (Quagga.initialized) {
|
|
Quagga.stop();
|
|
}
|
|
|
|
if (video.srcObject) {
|
|
video.srcObject.getTracks().forEach(track => track.stop());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
*/
|
|
function showError(message) {
|
|
errorMessage.textContent = '❌ ' + message;
|
|
errorMessage.classList.add('show');
|
|
setTimeout(() => {
|
|
errorMessage.classList.remove('show');
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Keyboard input callback
|
|
*/
|
|
function onKeyboardBarcodeScanned(barcode, inputType) {
|
|
onBarcodeScanned(barcode, inputType);
|
|
}
|
|
|
|
// Initialize keyboard barcode scanner
|
|
initializeBarcodeScanner(barcodeInput, onKeyboardBarcodeScanned);
|
|
|
|
// Mode toggle event listeners
|
|
keyboardModeBtn.addEventListener('click', switchToKeyboardMode);
|
|
cameraModeBtn.addEventListener('click', switchToCameraMode);
|
|
|
|
// Camera control event listeners
|
|
startCameraBtn.addEventListener('click', startCamera);
|
|
stopCameraBtn.addEventListener('click', stopCamera);
|
|
|
|
// Clear input button
|
|
clearInputBtn.addEventListener('click', () => {
|
|
barcodeInput.value = '';
|
|
barcodeInput.focus();
|
|
});
|
|
|
|
// Open console button
|
|
openConsoleBtn.addEventListener('click', () => {
|
|
console.log('%c📊 Barcode Scanner Console\n\nUse this console to view scanned barcodes and their details.\nStart scanning to see logs appear here.', 'color: #4CAF50; font-size: 14px; font-weight: bold; line-height: 1.8;');
|
|
alert('Press F12 (or Cmd+Option+I on Mac) to open the Developer Tools Console.\n\nScanned barcodes will appear in the Console tab.');
|
|
});
|
|
|
|
// Log initial message
|
|
console.log('%c🎯 Barcode Scanner Ready', 'color: #4CAF50; font-weight: bold; font-size: 16px;');
|
|
console.log('Scan a barcode to see it logged here with timestamp and input type detection.');
|
|
</script>
|
|
</body>
|
|
</html>
|