JSQR完整示例

本章节提供一个综合性的JSQR二维码解析应用示例,整合了前面所有章节中介绍的技术和技巧。通过这个示例,您可以学习如何在实际项目中使用JSQR库实现各种二维码解析功能,包括文件上传解析、摄像头实时扫描和从URL解析等。

下面是一个完整的二维码解析应用,它包含以下功能:

  • 从本地文件上传解析二维码
  • 从远程URL解析二维码
  • 从设备摄像头实时扫描二维码
  • 展示二维码的详细信息(内容、版本、纠错级别等)
  • 支持图像优化和解析配置调整
  • 提供完整的错误处理和用户反馈
预览图像

解析结果:

请选择图像文件进行解析
URL预览图像

解析结果:

请输入图像URL并点击解析按钮

扫描结果:

请点击开始扫描按钮

完整代码实现

下面是这个完整示例的代码实现,您可以直接复制使用或根据自己的需求进行修改:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSQR二维码解析应用</title>
    <!-- 引入JSQR库 -->
    <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
    <style>
        /* 这里是样式代码,与上面的style标签内容相同 */
    </style>
</head>
<body>
    <h1>JSQR二维码解析应用</h1>
    
    
    

    
    
<script> // 全局变量 let stream = null; let scanning = false; let requestId = null; let lastCameraResult = null; // DOM元素 const fileInput = document.getElementById('fileInput'); const previewImage = document.getElementById('previewImage'); const previewCanvas = document.getElementById('previewCanvas'); const previewCtx = previewCanvas.getContext('2d'); const fileResult = document.getElementById('fileResult'); const fileLoader = document.getElementById('fileLoader'); const inversionAttempts = document.getElementById('inversionAttempts'); const optimizeImage = document.getElementById('optimizeImage'); const showLocation = document.getElementById('showLocation'); const imageUrlInput = document.getElementById('imageUrlInput'); const parseUrlBtn = document.getElementById('parseUrlBtn'); const urlPreviewImage = document.getElementById('urlPreviewImage'); const urlResult = document.getElementById('urlResult'); const urlLoader = document.getElementById('urlLoader'); const urlInversionAttempts = document.getElementById('urlInversionAttempts'); const urlOptimizeImage = document.getElementById('urlOptimizeImage'); const cameraVideo = document.getElementById('cameraVideo'); const cameraCanvas = document.getElementById('cameraCanvas'); const cameraCtx = cameraCanvas.getContext('2d'); const startCameraBtn = document.getElementById('startCameraBtn'); const stopCameraBtn = document.getElementById('stopCameraBtn'); const cameraSelect = document.getElementById('cameraSelect'); const cameraResult = document.getElementById('cameraResult'); const cameraLoader = document.getElementById('cameraLoader'); const cameraInversionAttempts = document.getElementById('cameraInversionAttempts'); const continuousScan = document.getElementById('continuousScan'); const showScanLocation = document.getElementById('showScanLocation'); const scanRegion = document.getElementById('scanRegion'); const videoContainer = document.getElementById('videoContainer'); // 初始化选项卡 initTabs(); // 添加事件监听器 fileInput.addEventListener('change', handleFileUpload); parseUrlBtn.addEventListener('click', parseImageFromUrl); imageUrlInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') parseImageFromUrl(); }); startCameraBtn.addEventListener('click', startCameraScan); stopCameraBtn.addEventListener('click', stopCameraScan); window.addEventListener('resize', updateScanRegion); // 初始化选项卡函数 function initTabs() { const tabBtns = document.querySelectorAll('.tab-btn'); tabBtns.forEach(btn => { btn.addEventListener('click', function(e) { e.preventDefault(); // 移除所有活动状态 document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); // 添加当前活动状态 this.classList.add('active'); const tabId = this.getAttribute('href'); document.querySelector(tabId).classList.add('active'); }); }); } // 处理文件上传函数 function handleFileUpload(e) { const file = e.target.files[0]; if (!file) return; // 显示加载状态 fileLoader.style.display = 'block'; previewImage.style.display = 'none'; previewCanvas.style.display = 'none'; fileResult.textContent = '正在处理图像...'; // 创建FileReader读取文件 const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { // 绘制图像到Canvas previewCanvas.width = img.width; previewCanvas.height = img.height; previewCtx.drawImage(img, 0, 0); // 获取图像数据 let imageData = previewCtx.getImageData(0, 0, img.width, img.height); // 优化图像(如果选中) if (optimizeImage.checked) { imageData = optimizeImageData(imageData); } // 解析二维码 const options = { inversionAttempts: inversionAttempts.checked ? 'attemptBoth' : 'dontInvert' }; const code = jsQR(imageData.data, imageData.width, imageData.height, options); // 处理解析结果 fileLoader.style.display = 'none'; if (code) { // 显示解析结果 displayResult(fileResult, code); // 显示二维码位置(如果选中) if (showLocation.checked) { drawQRCodeLocation(previewCtx, code.location); previewCanvas.style.display = 'block'; } else { previewImage.src = e.target.result; previewImage.style.display = 'block'; } } else { // 解析失败 fileResult.innerHTML = '未能识别图像中的二维码'; previewImage.src = e.target.result; previewImage.style.display = 'block'; } }; img.onerror = function() { fileLoader.style.display = 'none'; fileResult.innerHTML = '无法加载图像文件'; }; img.src = e.target.result; }; reader.onerror = function() { fileLoader.style.display = 'none'; fileResult.innerHTML = '读取文件时发生错误'; }; reader.readAsDataURL(file); } // 从URL解析图像函数 function parseImageFromUrl() { const imageUrl = imageUrlInput.value.trim(); if (!imageUrl) { urlResult.innerHTML = '请输入有效的图像URL'; return; } // 显示加载状态 urlLoader.style.display = 'block'; urlPreviewImage.style.display = 'none'; urlResult.textContent = '正在加载并解析图像...'; // 创建图像对象 const img = new Image(); // 设置跨域属性 img.crossOrigin = 'anonymous'; img.onload = function() { // 创建临时Canvas const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); // 设置Canvas大小 tempCanvas.width = img.width; tempCanvas.height = img.height; // 绘制图像 tempCtx.drawImage(img, 0, 0); // 获取图像数据 let imageData = tempCtx.getImageData(0, 0, img.width, img.height); // 优化图像(如果选中) if (urlOptimizeImage.checked) { imageData = optimizeImageData(imageData); } // 解析二维码 const options = { inversionAttempts: urlInversionAttempts.checked ? 'attemptBoth' : 'dontInvert' }; const code = jsQR(imageData.data, imageData.width, imageData.height, options); // 处理解析结果 urlLoader.style.display = 'none'; urlPreviewImage.src = imageUrl; urlPreviewImage.style.display = 'block'; if (code) { // 显示解析结果 displayResult(urlResult, code); } else { // 解析失败 urlResult.innerHTML = '未能识别图像中的二维码'; } }; img.onerror = function() { urlLoader.style.display = 'none'; urlResult.innerHTML = '无法加载图像。请检查URL是否正确,或者服务器是否允许跨域访问。'; }; // 设置图像源 img.src = imageUrl; } // 开始摄像头扫描函数 function startCameraScan() { const cameraType = cameraSelect.value; // 获取视频流配置 const constraints = { video: { facingMode: cameraType, width: { ideal: 1280 }, height: { ideal: 720 } } }; // 显示加载状态 cameraLoader.style.display = 'block'; videoContainer.style.display = 'none'; cameraResult.textContent = '正在启动摄像头...'; // 请求摄像头权限 navigator.mediaDevices.getUserMedia(constraints) .then(function(mediaStream) { stream = mediaStream; cameraVideo.srcObject = stream; // 视频元数据加载完成后开始扫描 cameraVideo.onloadedmetadata = function() { // 设置Canvas大小 cameraCanvas.width = cameraVideo.videoWidth; cameraCanvas.height = cameraVideo.videoHeight; // 更新扫描区域 updateScanRegion(); // 开始扫描 scanning = true; cameraLoader.style.display = 'none'; videoContainer.style.display = 'inline-block'; cameraResult.textContent = '正在扫描二维码,请将二维码对准扫描区域...'; // 更新按钮状态 startCameraBtn.disabled = true; stopCameraBtn.disabled = false; // 开始扫描循环 scanFromCamera(); }; }) .catch(function(error) { cameraLoader.style.display = 'none'; handleCameraError(error); }); } // 停止摄像头扫描函数 function stopCameraScan() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } scanning = false; if (requestId) { cancelAnimationFrame(requestId); requestId = null; } // 更新UI状态 startCameraBtn.disabled = false; stopCameraBtn.disabled = true; cameraResult.textContent = '扫描已停止,请点击开始扫描按钮重新开始'; videoContainer.style.display = 'none'; lastCameraResult = null; } // 从摄像头扫描函数 function scanFromCamera() { if (!scanning) return; try { // 绘制当前视频帧 cameraCtx.drawImage(cameraVideo, 0, 0, cameraCanvas.width, cameraCanvas.height); // 获取扫描区域的图像数据 const scanRegionData = getScanRegionImageData(); // 解析二维码 const options = { inversionAttempts: cameraInversionAttempts.checked ? 'attemptBoth' : 'dontInvert' }; const code = jsQR(scanRegionData.data, scanRegionData.width, scanRegionData.height, options); if (code) { // 避免重复显示相同的结果 if (lastCameraResult !== code.data) { lastCameraResult = code.data; displayResult(cameraResult, code); // 如果未启用连续扫描,则停止扫描 if (!continuousScan.checked) { stopCameraScan(); return; } } // 显示二维码位置(如果选中) if (showScanLocation.checked) { // 注意:在摄像头扫描中,我们不直接在视频上绘制,而是在UI上显示扫描区域 } } } catch (error) { console.error('扫描错误:', error); cameraResult.innerHTML = `扫描过程中发生错误:${error.message}`; stopCameraScan(); } // 继续下一帧扫描 requestId = requestAnimationFrame(scanFromCamera); } // 获取扫描区域的图像数据 function getScanRegionImageData() { const rect = scanRegion.getBoundingClientRect(); const containerRect = videoContainer.getBoundingClientRect(); // 计算相对视频的位置和大小 const x = (rect.left - containerRect.left) * (cameraCanvas.width / containerRect.width); const y = (rect.top - containerRect.top) * (cameraCanvas.height / containerRect.height); const width = rect.width * (cameraCanvas.width / containerRect.width); const height = rect.height * (cameraCanvas.height / containerRect.height); // 获取扫描区域的图像数据 return cameraCtx.getImageData(x, y, width, height); } // 更新扫描区域的位置和大小 function updateScanRegion() { if (!videoContainer || !scanRegion) return; const containerWidth = videoContainer.offsetWidth; const containerHeight = videoContainer.offsetHeight; // 设置扫描区域的最大尺寸(不超过容器的70%) const maxSize = Math.min(containerWidth, containerHeight) * 0.7; const size = Math.min(250, maxSize); // 设置扫描区域的样式 scanRegion.style.width = `${size}px`; scanRegion.style.height = `${size}px`; } // 处理摄像头错误 function handleCameraError(error) { console.error('摄像头错误:', error); switch(error.name) { case 'NotAllowedError': cameraResult.innerHTML = '用户拒绝了摄像头访问请求。请在浏览器设置中允许访问摄像头。'; break; case 'NotFoundError': cameraResult.innerHTML = '未找到可用的摄像头设备。'; break; case 'NotReadableError': cameraResult.innerHTML = '摄像头被其他应用占用。请关闭其他使用摄像头的应用后重试。'; break; case 'OverconstrainedError': cameraResult.innerHTML = '无法满足摄像头参数要求。请尝试使用其他设备或浏览器。'; break; default: cameraResult.innerHTML = `访问摄像头时发生错误:${error.message}`; } } // 优化图像数据 function optimizeImageData(imageData) { const data = imageData.data; // 简单的对比度增强 const contrast = 1.2; // 对比度增强因子 const factor = (259 * (contrast + 255)) / (255 * (259 - contrast)); for (let i = 0; i < data.length; i += 4) { data[i] = factor * (data[i] - 128) + 128; // R data[i + 1] = factor * (data[i + 1] - 128) + 128; // G data[i + 2] = factor * (data[i + 2] - 128) + 128; // B // 保持alpha通道不变 } return imageData; } // 在Canvas上绘制二维码位置 function drawQRCodeLocation(ctx, location) { // 保存当前状态 ctx.save(); // 设置绘制样式 ctx.strokeStyle = '#00FF00'; ctx.lineWidth = 2; // 绘制二维码边框 ctx.beginPath(); ctx.moveTo(location.topLeftCorner.x, location.topLeftCorner.y); ctx.lineTo(location.topRightCorner.x, location.topRightCorner.y); ctx.lineTo(location.bottomRightCorner.x, location.bottomRightCorner.y); ctx.lineTo(location.bottomLeftCorner.x, location.bottomLeftCorner.y); ctx.closePath(); ctx.stroke(); // 绘制四个角的标记 drawCornerMarker(ctx, location.topLeftCorner, 'topLeft'); drawCornerMarker(ctx, location.topRightCorner, 'topRight'); drawCornerMarker(ctx, location.bottomRightCorner, 'bottomRight'); drawCornerMarker(ctx, location.bottomLeftCorner, 'bottomLeft'); // 恢复之前的状态 ctx.restore(); } // 绘制二维码角标记 function drawCornerMarker(ctx, corner, position) { ctx.fillStyle = '#00FF00'; let x, y, width, height; switch(position) { case 'topLeft': x = corner.x - 10; y = corner.y - 10; width = 20; height = 5; break; case 'topRight': x = corner.x - 5; y = corner.y - 10; width = 5; height = 20; break; case 'bottomRight': x = corner.x - 5; y = corner.y - 5; width = 15; height = 15; break; case 'bottomLeft': x = corner.x - 20; y = corner.y - 5; width = 20; height = 15; break; } ctx.fillRect(x, y, width, height); } // 显示二维码解析结果 function displayResult(element, code) { // 创建结果HTML const resultHtml = `
内容: ${escapeHtml(code.data)}
版本: ${code.version}
纠错级别: ${code.errorCorrectionLevel}
位置信息: 左上角:(${Math.round(code.location.topLeftCorner.x)}, ${Math.round(code.location.topLeftCorner.y)})
右上角:(${Math.round(code.location.topRightCorner.x)}, ${Math.round(code.location.topRightCorner.y)})
右下角:(${Math.round(code.location.bottomRightCorner.x)}, ${Math.round(code.location.bottomRightCorner.y)})
左下角:(${Math.round(code.location.bottomLeftCorner.x)}, ${Math.round(code.location.bottomLeftCorner.y)})
`; element.innerHTML = resultHtml; } // HTML转义函数 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } </script> </body> </html>

代码功能解析

上面的完整示例实现了一个功能齐全的二维码解析应用,以下是对主要功能模块的解析:

1. 选项卡界面

使用HTML、CSS和JavaScript实现了一个简单的选项卡界面,将不同的二维码解析功能分类展示:

  • 文件上传解析:允许用户上传本地图像文件进行二维码解析
  • URL解析:允许用户输入图像URL进行二维码解析
  • 摄像头扫描:允许用户使用设备摄像头实时扫描二维码

2. 核心功能模块

应用包含三个主要功能模块,每个模块对应一个选项卡:

文件上传解析模块

该模块允许用户选择本地图像文件进行二维码解析。主要功能包括:

  • 文件选择和预览
  • 图像优化处理
  • 二维码解析配置(如是否尝试颜色反转)
  • 二维码位置标记
  • 解析结果展示
URL解析模块

该模块允许用户输入包含二维码的图像URL进行解析。主要功能包括:

  • URL输入和验证
  • 跨域图像加载处理
  • 图像优化和二维码解析
  • 解析结果展示
摄像头扫描模块

该模块允许用户使用设备摄像头实时扫描二维码。主要功能包括:

  • 摄像头权限申请和管理
  • 前后摄像头切换
  • 实时视频流处理和二维码解析
  • 扫描区域显示
  • 连续扫描和单次扫描模式
  • 实时扫描结果展示
  • 摄像头错误处理

3. 辅助功能模块

除了核心功能模块外,应用还包含一些辅助功能模块,用于提高用户体验和解析成功率:

图像优化模块

提供了简单的图像优化功能,通过增强对比度等方式提高二维码的识别率。

错误处理模块

提供了全面的错误处理机制,包括文件读取错误、图像加载错误、摄像头访问错误等,并向用户提供友好的错误提示。

结果展示模块

以结构化的方式展示二维码解析结果,包括二维码内容、版本、纠错级别和位置信息等。

如何扩展这个应用

这个完整示例可以作为您开发二维码解析应用的基础。以下是一些可能的扩展方向:

  • 多二维码识别:实现同时识别图像中的多个二维码的功能
  • 二维码内容验证:添加验证二维码内容是否符合特定格式的功能
  • 扫描历史记录:添加扫描历史记录功能,保存和管理用户的扫描结果
  • 批量处理:实现批量上传和解析多个图像文件的功能
  • 导出功能:添加将扫描结果导出为CSV、JSON等格式的功能
  • 二维码生成:添加生成二维码的功能,使应用成为一个完整的二维码工具集

总结

本章节提供了一个完整的JSQR二维码解析应用示例,整合了前面所有章节中介绍的技术和技巧。这个示例展示了如何在实际项目中使用JSQR库实现各种二维码解析功能,包括文件上传解析、摄像头实时扫描和从URL解析等。

通过学习和理解这个示例,您将能够掌握JSQR库的核心功能和高级用法,并能够根据自己的需求开发出功能丰富、性能优良的二维码解析应用。

在实际开发中,您可能需要根据具体需求对示例代码进行修改和扩展,添加更多的功能和优化。希望这个示例能够帮助您快速入门JSQR库,并在您的项目中发挥作用。