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