id-scanner-lib
Version:
一款纯前端实现的TypeScript身份证&二维码识别库,无需后端支持,所有处理在浏览器端完成,新增图像批处理与优化
168 lines (149 loc) • 3.93 kB
text/typescript
/**
* @file 二维码扫描模块
* @description 提供实时二维码扫描和识别功能
* @module QRScanner
*/
import jsQR from "jsqr"
import { Camera } from "../utils/camera"
/**
* 二维码扫描器配置选项
*
* @interface QRScannerOptions
* @property {number} [scanInterval] - 扫描间隔时间(毫秒),默认为200ms
* @property {Function} [onScan] - 扫描成功回调函数
* @property {Function} [onError] - 错误处理回调函数
*/
export interface QRScannerOptions {
scanInterval?: number
onScan?: (result: string) => void
onError?: (error: Error) => void
}
/**
* 二维码扫描器类
*
* 提供实时扫描和识别摄像头中的二维码的功能
*
* @example
* ```typescript
* // 创建二维码扫描器
* const qrScanner = new QRScanner({
* scanInterval: 100, // 每100ms扫描一次
* onScan: (result) => {
* console.log('扫描到二维码:', result);
* },
* onError: (error) => {
* console.error('扫描错误:', error);
* }
* });
*
* // 启动扫描
* const videoElement = document.getElementById('video') as HTMLVideoElement;
* await qrScanner.start(videoElement);
*
* // 停止扫描
* qrScanner.stop();
* ```
*/
export class QRScanner {
private camera: Camera
private scanning = false
private scanTimer: number | null = null
/**
* 创建二维码扫描器实例
*
* @param {QRScannerOptions} [options] - 扫描器配置选项
*/
constructor(private options: QRScannerOptions = {}) {
this.options = {
scanInterval: 200,
...options,
}
this.camera = new Camera()
}
/**
* 启动二维码扫描
*
* 初始化相机并开始连续扫描视频帧中的二维码
*
* @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
* @returns {Promise<void>} 启动完成的Promise
* @throws 如果无法访问相机,将通过onError回调报告错误
*/
async start(videoElement: HTMLVideoElement): Promise<void> {
try {
await this.camera.initialize(videoElement)
this.scanning = true
this.scan()
} catch (error) {
if (this.options.onError) {
this.options.onError(
error instanceof Error ? error : new Error(String(error))
)
}
}
}
/**
* 执行一次二维码扫描
*
* 内部方法,捕获当前视频帧并尝试识别其中的二维码
*
* @private
*/
private scan(): void {
if (!this.scanning) return
const imageData = this.camera.captureFrame()
if (imageData) {
const code = jsQR(imageData.data, imageData.width, imageData.height)
if (code && this.options.onScan) {
this.options.onScan(code.data)
}
}
this.scanTimer = window.setTimeout(
() => this.scan(),
this.options.scanInterval
)
}
/**
* 停止二维码扫描
*
* 停止扫描循环并释放相机资源
*/
stop(): void {
this.scanning = false
if (this.scanTimer) {
clearTimeout(this.scanTimer)
this.scanTimer = null
}
this.camera.release()
}
/**
* 处理图像数据中的二维码
*
* @param {ImageData} imageData - 要处理的图像数据
* @returns {string | null} 识别到的二维码内容,如未识别到则返回null
*/
processImageData(imageData: ImageData): string | null {
try {
if (
!imageData ||
!imageData.data ||
imageData.width <= 0 ||
imageData.height <= 0
) {
throw new Error("无效的图像数据")
}
const code = jsQR(imageData.data, imageData.width, imageData.height)
if (code && code.data) {
return code.data
}
return null
} catch (error) {
if (this.options.onError) {
this.options.onError(
error instanceof Error ? error : new Error(String(error))
)
}
return null
}
}
}