web-wasm-barcode-reader
v1.5.0
Published
Barcode scanner powered by ZBar compiled to WebAssembly
Maintainers
Readme
Web WASM Barcode Reader
A browser-based barcode scanner powered by ZBar compiled to WebAssembly. Works on any browser, including Safari/iOS where the native Barcode Detection API isn't available.
Built with TypeScript, Vite, and a wrapper-ready BarcodeScanner class designed for easy integration into React, Vue, or any frontend framework.
Based on the blog post: Using the ZBar barcode scanning suite in the browser with WebAssembly.
My take: Barcode Scanning on iOS: The Missing Web API and a WebAssembly Solution.
NPM: https://www.npmjs.com/package/web-wasm-barcode-reader
npm i web-wasm-barcode-readerFeatures
- Real-time camera scanning with configurable scan region and interval
- Rotation-based skew correction — scans at 0°, +30°, and -30° with early exit, so tilted barcodes are detected without perfect alignment
- Animated CSS overlay — dark viewfinder mask, corner brackets, and a sweeping laser line (replaces the old static PNG)
- Audible beep on detection (base64-encoded, no external file)
- Torch toggle for devices that support flashlight control
- Debug preview panel — shows all three rotation passes side-by-side with detection highlighting
- Framework-agnostic —
BarcodeScannerclass withstart()/stop()lifecycle, constructor has no side effects (React strict-mode safe)
Supported Barcode Formats
ZBar supports the following symbologies:
| 1D | 2D | |---|---| | EAN-13, EAN-8 | QR Code | | UPC-A, UPC-E | | | ISBN-10, ISBN-13 | | | Code 128, Code 39, Code 93 | | | Interleaved 2 of 5 (I25) | | | DataBar | |
Note: Data Matrix, PDF417, and Aztec codes are not supported by ZBar.
Quick Start
npm install
npm run devOpen the URL shown by Vite (typically http://localhost:5173). Grant camera access and point at a barcode within the scan region.
Production Build
npm run build
npm run previewThe build output goes to dist/. Type-checking runs automatically before the Vite build.
Project Structure
web-wasm-barcode-reader/
├── public/
│ ├── a.out.js # Emscripten JS glue (pre-compiled)
│ └── a.out.wasm # ZBar WASM binary (pre-compiled)
├── src/
│ ├── scanner.ts # BarcodeScanner class — core lifecycle + scan loop
│ ├── overlay.ts # Animated CSS scan overlay (mask, brackets, laser)
│ ├── main.ts # Demo entry point
│ └── types/
│ └── emscripten.d.ts # TypeScript declarations for the Emscripten Module
├── index.html # Demo page
├── scan.c # C source — ZBar image scanning + WASM buffer management
├── library.js # Emscripten JS library — bridges WASM results to JS
├── package.json
├── tsconfig.json # strict: true, noImplicitAny: true
└── vite.config.tsArchitecture
How a Scan Works
Camera frame
│
├─ drawImage() ──► Offscreen canvas (crop scan region from video)
│ │
│ ├─ Rotate 0° ──► grayscale ──► WASM scan_image()
│ ├─ Rotate +30° ──► grayscale ──► WASM scan_image() (only if 0° missed)
│ └─ Rotate -30° ──► grayscale ──► WASM scan_image() (only if ±30° missed)
│
└─ On detection: polygon overlay + beep + onDetect callbackWASM Bridge
The scanner communicates with ZBar through three C functions exposed via Emscripten:
| C function | Purpose |
|---|---|
| create_buffer(w, h) | malloc a buffer on the WASM heap for image data |
| scan_image(ptr, w, h) | Run ZBar on grayscale pixels at ptr. Calls js_output_result on hit. |
| destroy_buffer(ptr) | free a buffer (unused — ZBar frees it internally via zbar_image_free_data) |
library.js defines js_output_result, which reads symbol name, data, and polygon coordinates from the WASM heap and forwards them to Module.processResult — a callback set by the BarcodeScanner class.
Important:
scan_imageregisters the buffer withzbar_image_free_dataas the cleanup handler, so ZBar frees the buffer when the image is destroyed. A fresh buffer must be allocated for everyscan_imagecall — reusing a pointer is use-after-free.
Usage as npm Package
The library requires the Emscripten WASM glue script (a.out.js) to be loaded before the scanner is started. The WASM binary (a.out.wasm) is fetched automatically by the glue script at runtime.
1. Copy the WASM files into your public/static directory
After installing, copy the WASM assets to a location your web server can serve:
cp node_modules/web-wasm-barcode-reader/public/a.out.js public/
cp node_modules/web-wasm-barcode-reader/public/a.out.wasm public/Or with a bundler, add a copy step to your build pipeline.
2. Load the glue script before using the scanner
Add a <script> tag in your HTML before your app bundle:
<script src="/a.out.js"></script>
<script type="module" src="/your-app.js"></script>Important:
a.out.jsmust be loaded as a classic (non-module) script because it sets up the globalModuleobject that the scanner depends on.
3. Import and use
import { BarcodeScanner } from 'web-wasm-barcode-reader';
const scanner = new BarcodeScanner({
container: document.getElementById('scanner-mount')!,
onDetect: (result) => {
console.log(result.symbol, result.data);
},
});
await scanner.start();
// later...
scanner.stop();If a.out.js is not loaded, start() will reject with a clear error message.
API
BarcodeScanner
import { BarcodeScanner } from 'web-wasm-barcode-reader';
const scanner = new BarcodeScanner({
container: document.getElementById('scanner-mount')!,
onDetect: (result) => {
console.log(result.symbol, result.data);
},
});
await scanner.start();
// later...
scanner.stop();ScannerOptions
| Option | Type | Default | Description |
|---|---|---|---|
| container | HTMLElement | required | Element to mount the scanner into |
| onDetect | (result: ScanResult) => void | required | Called on each barcode detection |
| onError | (error: Error) => void | console.error | Called on unrecoverable errors |
| scanInterval | number | 150 | Milliseconds between scan attempts |
| beepOnDetect | boolean | true | Play an audible beep on detection |
| facingMode | 'environment' \| 'user' | 'environment' | Camera facing mode |
| scanRegion | { width: number; height: number } | { width: 0.702, height: 0.242 } | Scan region as fraction of container |
| previewCanvas | HTMLCanvasElement | undefined | Optional canvas for rotation debug preview |
ScanResult
| Field | Type | Description |
|---|---|---|
| symbol | string | Barcode symbology (e.g. "EAN-13", "QR-Code") |
| data | string | Decoded barcode content |
| polygon | number[] | Flat [x1, y1, x2, y2, ...] bounding polygon in container coordinates |
Methods
| Method | Description |
|---|---|
| start(): Promise<void> | Initialize camera, WASM, and start scanning |
| stop(): void | Stop scanning, release camera, remove DOM elements |
| toggleTorch(): Promise<boolean> | Toggle flashlight, returns new state |
| isRunning: boolean | Whether the scanner is currently active |
Framework Integration
The BarcodeScanner constructor has no side effects — it only stores configuration. This makes it safe to use with React strict mode, Vue lifecycle hooks, etc.
React Example
import { useEffect, useRef } from 'react';
import { BarcodeScanner } from 'web-wasm-barcode-reader';
function Scanner({ onScan }: { onScan: (data: string) => void }) {
const mountRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const scanner = new BarcodeScanner({
container: mountRef.current!,
onDetect: (result) => onScan(result.data),
});
scanner.start();
return () => scanner.stop();
}, [onScan]);
return <div ref={mountRef} style={{ width: 400, height: 400 }} />;
}Recompiling the WASM Module
The public/a.out.js and public/a.out.wasm files are pre-compiled. To rebuild them, you need Emscripten and ZBar installed:
emcc scan.c \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_scan_image", "_create_buffer", "_destroy_buffer"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap", "UTF8ToString"]' \
--js-library library.js \
-lzbar \
-o public/a.out.jsThe exact flags may vary depending on your Emscripten version and ZBar installation path.
Scripts
| Script | Command | Description |
|---|---|---|
| dev | npm run dev | Start Vite dev server with HMR |
| build | npm run build | Type-check + production build to dist/ |
| preview | npm run preview | Serve the production build locally |
Browser Requirements
- getUserMedia (camera access) — all modern browsers
- WebAssembly — all modern browsers
- OffscreenCanvas — Chrome 69+, Firefox 105+, Safari 16.4+
- HTTPS or localhost (required for camera access)
License
See the ZBar license for the scanning library. The JavaScript/TypeScript wrapper code in this repository is unlicensed.
