Lightweight WASM barcode scanner (495KB) - DataMatrix, QR Code, configurable preloading, PDF support, headless API
npm install prescription-scanner





![Zero Dependencies]()

Lightweight WASM barcode scanner for web applications. Supports DataMatrix and QR Code.
v1.1.5 - scanBounds API for custom scan areas!
- Single WASM file with DataMatrix and QR Code support
- Zero dependencies - pure vanilla JavaScript
- Headless mode (default) - use without UI for custom integrations
- Configurable WASM preloading - lazy, idle, eager, or manual
- PDF support - scan barcodes from PDF files
- Multi-code detection - find all codes in one scan
- Image enhancement - auto upscaling & contrast for better detection
- Works with any framework (React, Vue, Angular, vanilla JS)
- Mobile-optimized camera handling
``bash`
npm install prescription-scanner
The scanner runs in headless mode by default - no UI, just the scanning API:
`typescript
import { PrescriptionScanner } from 'prescription-scanner';
const scanner = new PrescriptionScanner({
preload: 'idle', // Load WASM when browser is idle
onReady: () => {
console.log('WASM loaded, scanner ready!');
startButton.disabled = false;
},
onScan: (result) => {
console.log('Found:', result.data, result.format);
}
});
// === Camera Scanning ===
// Start camera in your container
const video = await scanner.startCamera(myContainer);
// Stop scanning and cleanup
scanner.stop();
// === Static Scanning ===
const results = await scanner.scanImage(imgElement);
const results = await scanner.scanImageData(imageData);
const results = await scanner.scanCanvas(canvasElement);
const results = await scanner.scanPDF(pdfFile);
`
The WASM module (~450KB) can be loaded with different strategies:
`typescript
// Load when browser is idle (recommended for best UX)
const scanner = new PrescriptionScanner({
preload: 'idle',
onReady: () => button.disabled = false
});
// Load immediately on instantiation
const scanner = new PrescriptionScanner({
preload: 'eager'
});
// Load on first use (default)
const scanner = new PrescriptionScanner({
preload: 'lazy' // or omit - this is the default
});
// Manual loading
const scanner = new PrescriptionScanner({
preload: false
});
await scanner.preload(); // or scanner.init()
`
| Strategy | When | Use Case |
|----------|------|----------|
| 'idle' | Browser idle | Best UX - preload without blocking |'eager'
| | Immediately | When scanner is primary feature |'lazy'
| | First use | Minimal initial load |false
| | Manual | Full control over timing |
#### React Example with Loading State
`tsx
const [isReady, setIsReady] = useState(false);
const scannerRef = useRef
useEffect(() => {
const scanner = new PrescriptionScanner({
preload: 'idle', // Loads WASM when browser is idle
onReady: () => setIsReady(true),
onScan: (result) => console.log(result)
});
scannerRef.current = scanner;
return () => scanner.stop();
}, []);
// Button shows loading state until WASM is ready
`
> Note: The WASM module is cached globally. Once loaded by any scanner instance, subsequent instances can use it immediately without reloading.
`html`
`typescript
interface ScannerOptions {
// WASM Loading
preload?: 'idle' | 'eager' | 'lazy' | false; // Preload strategy (default: 'lazy')
// Formats to detect
formats?: ('DataMatrix' | 'QRCode')[];
// Scan area (optional - default: full frame)
scanBounds?: ScanBounds;
// Callbacks
onReady?: () => void; // Called when WASM is loaded
onScan?: (result: ScanResult) => void; // Called for each detected code
onError?: (error: Error) => void; // Called on errors
}
interface ScanBounds {
x: number; // X offset (0-1 relative or pixels)
y: number; // Y offset (0-1 relative or pixels)
width: number; // Width (0-1 relative or pixels)
height: number; // Height (0-1 relative or pixels)
}
interface ScanResult {
data: string;
format: 'DataMatrix' | 'QRCode';
timestamp: number;
points: { x: number; y: number }[]; // Code position in full frame
}
`
Creates a new scanner instance.
#### scanner.preload()
Preload WASM module in background. Returns Promise. Triggers onReady when complete.
#### scanner.init()
Initialize scanner and load WASM. Returns Promise. Triggers onReady when complete.
#### scanner.isReady()
Returns true if WASM is loaded and scanner is ready.
#### scanner.startCamera(container)
Creates a video element in the container, requests camera access, and starts scanning. Returns the video element for custom styling.
#### scanner.start(videoElement)
Start scanning on an existing video element (must have camera stream attached).
#### scanner.stop()
Stop scanning and clean up camera stream and video element.
#### scanner.isScanning()
Returns true if currently scanning.
#### scanner.scanImage(image)
Scan an HTMLImageElement. Returns Promise.
#### scanner.scanImageData(imageData)
Scan ImageData directly. Returns Promise.
#### scanner.scanCanvas(canvas)
Scan an HTMLCanvasElement. Returns Promise.
#### scanner.scanPDF(file)
Scan all pages of a PDF file. Returns Promise.
#### scanner.getResults()
Returns all scanned results so far.
#### scanner.clearResults()
Clears all stored results, allowing the same codes to be scanned again.
#### scanner.setScanBounds(bounds)
Set the scan area. Values 0-1 are relative to frame size, >1 are absolute pixels.
`typescript
// Scan center 50% of frame
scanner.setScanBounds({ x: 0.25, y: 0.25, width: 0.5, height: 0.5 });
// Scan full frame (default)
scanner.setScanBounds(null);
`
#### scanner.getScanBounds()
Returns current scan bounds or null if scanning full frame.
#### scanner.getComputedBounds()
Returns scan bounds in pixels based on current video dimensions.
#### scanner.destroy()
Cleanup - stops camera and removes event listeners.
PDF.js is loaded dynamically from CDN when needed (~200 KB). No additional setup required.
`typescript
import { processPDF, isPDF } from 'prescription-scanner';
// Check if file is PDF
if (isPDF(file)) {
const pages = await processPDF(file, {
scale: 2, // Render at 2x for better recognition
maxPages: 10, // Process max 10 pages
onProgress: (current, total) => {
console.log(Processing page ${current}/${total});`
}
});
}
| Format | Use Case |
|--------|----------|
| DataMatrix | German prescriptions (eRezept), medical packaging |
| QR Code | General purpose, URLs, vCards |
- Node.js 18+
- Emscripten SDK (for WASM build)
`bash`
npm run build
`bash`
npm run build:wasm
| File | Size |
|------|------|
| scanner.wasm | 442 KB |
| scanner.js | 53 KB |
| index.js (ESM) | 18 KB |
| PDF.js (CDN, lazy) | ~200 KB |
| Core Total | ~495 KB |
WASM loading is configurable via preload option: 'lazy' (default), 'idle', 'eager', or false.
PDF.js is lazy-loaded only when processing PDFs.
- Chrome 66+
- Firefox 62+
- Safari 12+
- Edge 79+
Requires WebAssembly and getUserMedia (camera) support.
option to limit scan area within video frame
- Added setScanBounds(), getScanBounds(), getComputedBounds() methods
- Bounds support relative (0-1) and absolute pixel values
- Code positions in results are adjusted to full frame coordinates
- Added ScanBounds demo page$3
- Added clearResults() method to allow rescanning same codes
- Fixed camera restart bug (camera couldn't restart after stopping)
- Renamed "Built-in Modal Demo" to "Lazyload Demo"
- Added unit tests$3
- Configurable WASM preloading: 'idle', 'eager', 'lazy', or false
- onReady callback: Get notified when WASM is loaded
- Headless mode is now the default (headless: true)
- Added camera API: startCamera(container), start(video), stop(), isScanning()
- Added preload methods: preload(), init(), isReady()
- Added static scan methods: scanImage(), scanImageData(), scanCanvas(), scanPDF()
- Fixed modal scrolling when many codes are scanned
- Added proper Apache 2.0 attribution for zxing-cpp$3
- Fixed README: clarified API classes$3
- Updated README and package.json documentation$3
- Added PDF support with PDF.js
- Added multi-code detection
- Added file upload UI (drag & drop)
- Added image enhancement (upscaling, contrast adjustment)
- Added onMultiScan callback
- Added getResults() method
- Added enhanceForScanning()` utilityThis package includes zxing-cpp for barcode decoding, licensed under Apache 2.0. See THIRD-PARTY-NOTICES for details.
MIT (this package) AND Apache-2.0 (zxing-cpp)