High-performance QR code scanner for the web, powered by ZXing-C++ WebAssembly. Drop-in replacement for nimiq/qr-scanner with significantly better decoding of dense QR codes.
- No forced downscale - feeds high-resolution frames to the decoder (720-1080px+)
- WASM decoder - ZXing-C++ compiled to WebAssembly, ~2x faster than pure-JS decoders
- Web Worker - decoding runs off the main thread, keeping UI at 60fps
tryHardermode - additional processing passes for hard-to-read codes
bun add @agicash/qr-scanner
# or
npm install @agicash/qr-scannerimport QrScanner from '@agicash/qr-scanner';
const scanner = new QrScanner(videoElement, (result) => {
console.log(result.data); // decoded string
console.log(result.cornerPoints); // QR code corner positions
}, {
highlightScanRegion: true,
highlightCodeOutline: true,
});
await scanner.start();
// Later...
scanner.destroy();import QrScanner from '@agicash/qr-scanner';
const result = await QrScanner.scanImage(fileOrBlobOrUrl);
console.log(result.data);new QrScanner(videoElement, onDecode, options?)| Option | Type | Default | Description |
|---|---|---|---|
onDecodeError |
(error) => void |
no-op | Called when no QR code found |
calculateScanRegion |
(video) => ScanRegion |
centered 2/3 square | Define the scan crop area |
preferredCamera |
'environment' | 'user' | string |
'environment' |
Camera to use |
maxScansPerSecond |
number |
15 |
Max decode attempts per second |
highlightScanRegion |
boolean |
false |
Show scan region overlay |
highlightCodeOutline |
boolean |
false |
Highlight detected QR code |
overlay |
HTMLDivElement |
- | Custom overlay element |
cameraResolution |
object |
{ width: { ideal: 1920 }, height: { ideal: 1080 } } |
Camera resolution |
decoderOptions |
Partial<ReaderOptions> |
- | zxing-wasm reader options |
scanner.start(): Promise<void>
scanner.stop(): void
scanner.destroy(): void
scanner.pause(stopStreamImmediately?): Promise<boolean>
scanner.setCamera(facingModeOrDeviceId): Promise<void>
scanner.hasFlash(): Promise<boolean>
scanner.isFlashOn(): boolean
scanner.toggleFlash(): Promise<void>
scanner.turnFlashOn(): Promise<void>
scanner.turnFlashOff(): Promise<void>
scanner.setInversionMode(mode): voidQrScanner.hasCamera(): Promise<boolean>
QrScanner.listCameras(requestLabels?): Promise<Camera[]>
QrScanner.scanImage(source, options?): Promise<ScanResult>
QrScanner.preload(): Promise<void>
QrScanner.configureWasm(overrides): void
QrScanner.setWorkerUrl(url): void
QrScanner.setDebug(enabled): voidThe library includes performance profiling logs (camera acquisition timing, best-camera selection, etc.) that are off by default. Enable them at runtime for debugging:
QrScanner.setDebug(true); // logs appear in console
QrScanner.setDebug(false); // back to silent (default)The library uses a Web Worker for off-thread QR decoding. The worker script (dist/worker.js) is resolved automatically by modern bundlers (Vite, webpack 5, Parcel) via new URL('./worker.js', import.meta.url).
For CJS consumers or non-standard setups, set the worker URL manually:
QrScanner.setWorkerUrl('/assets/@agicash/qr-scanner/dist/worker.js');By default, the WASM binary (~919 KB) loads from jsDelivr CDN. To self-host:
QrScanner.configureWasm({
locateFile: (filename) => `/wasm/${filename}`,
});| qr-scanner | @agicash/qr-scanner |
|---|---|
returnDetailedScanResult: true |
Always returns ScanResult |
ScanRegion.downScaledWidth/Height |
Removed - no downscaling |
createQrEngine() |
Removed - engine is internal |
setGrayscaleWeights() |
Removed - handled by WASM |
DEFAULT_CANVAS_SIZE |
Removed - no canvas downscale |
| Multiple constructor overloads | Single constructor |
Chrome 80+, Safari 16+, Firefox 80+, Edge 80+
MIT