How to Build a Web App to Scan Barcodes, Documents, and MRZ in HTML5 and JavaScript
In the dynamic world of web development, the ability to seamlessly integrate diverse functionalities into web applications has become a necessity. Technologies such as barcode detection, Machine Readable Zones (MRZ) recognition, and document rectification are widely used in various industries, including retail, travel, and document management. This article will guide you through the process of implementing these functionalities in your web applications using the Dynamsoft Capture Vision JavaScript SDK.
Demo Video: Web Scanner for Barcode, QR Code, MRZ, and Document
Online Demo
https://yushulx.me/javascript-barcode-qr-code-scanner/examples/barcode_mrz_document/
Prerequisites
- Obtain a License: Get a 30-day FREE Trial License for Dynamsoft Capture Vision.
-
Include the SDK: Add the Dynamsoft Capture Vision Bundle Module to your HTML page.
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@3.0.3001/dist/dcv.bundle.min.js"></script>
Configuring License Key, Input Source and Scanning Options
License Activation

The core WebAssembly (WASM) modules will start loading only after the user clicks the Activate SDK button and enters a valid license key.
<div>
<label>
Get a License key from <a href="https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform"
target="_blank">here</a>
</label>
<input type="text" id="license_key"
value="LICENSE-KEY"
placeholder="LICENSE-KEY">
<button onclick="activate()">Activate SDK</button>
</div>
<script>
let cvr;
let reader;
let cameraEnhancer;
let isSDKReady = false;
let img = new Image();
let globalPoints;
let cameras;
let resolution;
let isDetecting = false;
let isCaptured = false;
let parser;
async function activate() {
toggleLoading(true);
let divElement = document.getElementById("license_key");
let licenseKey = divElement.value == "" ? divElement.placeholder : divElement.value;
try {
await Dynamsoft.License.LicenseManager.initLicense(
licenseKey,
true
);
Dynamsoft.Core.CoreModule.loadWasm(["DBR", "DLR", "DDN"]);
parser = await Dynamsoft.DCP.CodeParser.createInstance();
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD1_ID");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_ID");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_VISA");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_PASSPORT");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_VISA");
await Dynamsoft.CVR.CaptureVisionRouter.appendModelBuffer("MRZCharRecognition");
await Dynamsoft.CVR.CaptureVisionRouter.appendModelBuffer("MRZTextLineRecognition");
cvr = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
cvr.addResultReceiver({
onCapturedResultReceived: (result) => {
showCameraResult(result);
},
});
isSDKReady = true;
}
catch (ex) {
console.error(ex);
}
toggleLoading(false);
}
</script>
Input Source Selection
Based on the input source selected from a dropdown list, the application can dynamically switch between different containers.
<div class="row">
<div>
<select onchange="selectChanged()" id="dropdown">
<option value="file">File</option>
<option value="camera">Camera</option>
</select>
<input type="checkbox" id="barcode_checkbox" checked>Barcode
<input type="checkbox" id="mrz_checkbox">MRZ
<input type="checkbox" id="document_checkbox" onchange="checkChanged()">Document
</div>
</div>
<script>
async function selectChanged() {
if (dropdown.value === 'file') {
if (cameraEnhancer != null) {
closeCamera(cameraEnhancer);
if (cvr != null) {
await cvr.stopCapturing();
}
scanButton.innerHTML = "Scan";
isDetecting = false;
}
let divElement = document.getElementById("file_container");
divElement.style.display = "block";
divElement = document.getElementById("camera_container");
divElement.style.display = "none";
}
else {
if (cameraEnhancer == null) {
await initCamera();
}
let divElement = document.getElementById("camera_container");
divElement.style.display = "block";
divElement = document.getElementById("file_container");
divElement.style.display = "none";
await cameraChanged();
}
}
</script>
Detecting Barcodes, MRZ, and Documents from Image Files
The following steps outline the process for detecting barcodes, MRZ (Machine Readable Zone), and documents within an image file:
-
Construct the UI (User Interface) for file input and result display. This involves creating a section for users to upload an image file and another section to show the detection outcomes.
<div class="container" id="file_container"> <div> <input type="file" id="pick_file" accept="image/*" /> <button onclick="detect()">Detect</button> </div> <div class="row"> <div class="imageview"> <img id="image_file" src="default.png" /> <canvas id="overlay_canvas" class="overlay"></canvas> </div> </div> <div class="row"> <div> <textarea id="detection_result"></textarea> </div> </div> <div class="row"> <div> <img id="document-rectified-image" /> </div> </div> </div> -
Load the uploaded image onto an HTML canvas. The canvas serves not only to display the image but also to overlay the detection results, providing a visual representation of what has been identified.
document.getElementById("pick_file").addEventListener("change", function () { let currentFile = this.files[0]; if (currentFile == null) { return; } var fr = new FileReader(); fr.onload = function () { loadImage2Canvas(fr.result); } fr.readAsDataURL(currentFile); }); function loadImage2Canvas(base64Image) { imageFile.src = base64Image; img.src = base64Image; img.onload = async function () { let width = img.width; let height = img.height; overlayCanvas.width = width; overlayCanvas.height = height; targetCanvas.width = width; targetCanvas.height = height; ... }; } -
Utilize Dynamsoft Capture Vision JavaScript APIs to detect the barcode, MRZ, and document in the image. Once detected, draw the results directly on the canvas to visually indicate the locations of the detected items.
let selectedMode = document.querySelector('input[name="scanMode"]:checked').value; let context = overlayCanvas.getContext('2d'); context.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); try { if (selectedMode == "barcode") { await cvr.resetSettings(); cvr.capture(img.src, "ReadBarcodes_Default").then((result) => { showFileResult(selectedMode, context, result, img, Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE); }); } else if (selectedMode == "mrz") { await cvr.initSettings("./full.json"); cvr.capture(img.src, "ReadMRZ").then((result) => { showFileResult(selectedMode, context, result, img, Dynamsoft.Core.EnumCapturedResultItemType.CRIT_TEXT_LINE); }); } else if (selectedMode == "document") { await cvr.resetSettings(); cvr.capture(img.src, "DetectDocumentBoundaries_Default").then((result) => { showFileResult(selectedMode, context, result, img, Dynamsoft.Core.EnumCapturedResultItemType.CRIT_DETECTED_QUAD); }); } } catch (ex) { console.error(ex); } async function showFileResult(selectedMode, context, result, img, type) { let parseResults = ''; let detection_result = document.getElementById('detection_result'); detection_result.innerHTML = ""; let txts = []; let items = result.items; if (items.length > 0) { for (var i = 0; i < items.length; ++i) { if (items[i].type !== type) { continue; } let item = items[i]; txts.push(item.text); localization = item.location; context.strokeStyle = '#ff0000'; context.lineWidth = 2; let points = localization.points; globalPoints = points; context.beginPath(); context.moveTo(points[0].x, points[0].y); context.lineTo(points[1].x, points[1].y); context.lineTo(points[2].x, points[2].y); context.lineTo(points[3].x, points[3].y); context.closePath(); context.stroke(); if (selectedMode == "barcode") { if (txts.length > 0) { detection_result.innerHTML += txts.join('\n') + '\n\n'; } else { detection_result.innerHTML += "Recognition Failed\n"; } } else if (selectedMode == "mrz") { if (txts.length > 0) { detection_result.innerHTML += txts.join('\n') + '\n\n'; parseResults = await parser.parse(item.text); detection_result.innerHTML += JSON.stringify(extractMrzInfo(parseResults)); } else { detection_result.innerHTML += "Recognition Failed\n"; } } else if (selectedMode == "document") { openEditor(img.src); } } } else { detection_result.innerHTML += "Nothing found\n"; } }
Extracting MRZ Information
After recognizing the MRZ, you can parse the MRZ string to extract important information, including the individual’s name, passport number, date of birth, and expiration date.
function extractMrzInfo(result) {
const parseResultInfo = {};
let type = result.getFieldValue("documentCode");
parseResultInfo['Document Type'] = JSON.parse(result.jsonString).CodeType;
let nation = result.getFieldValue("issuingState");
parseResultInfo['Issuing State'] = nation;
let surName = result.getFieldValue("primaryIdentifier");
parseResultInfo['Surname'] = surName;
let givenName = result.getFieldValue("secondaryIdentifier");
parseResultInfo['Given Name'] = givenName;
let passportNumber = type === "P" ? result.getFieldValue("passportNumber") : result.getFieldValue("documentNumber");
parseResultInfo['Passport Number'] = passportNumber;
let nationality = result.getFieldValue("nationality");
parseResultInfo['Nationality'] = nationality;
let gender = result.getFieldValue("sex");
parseResultInfo["Gender"] = gender;
let birthYear = result.getFieldValue("birthYear");
let birthMonth = result.getFieldValue("birthMonth");
let birthDay = result.getFieldValue("birthDay");
if (parseInt(birthYear) > (new Date().getFullYear() % 100)) {
birthYear = "19" + birthYear;
} else {
birthYear = "20" + birthYear;
}
parseResultInfo['Date of Birth (YYYY-MM-DD)'] = birthYear + "-" + birthMonth + "-" + birthDay;
let expiryYear = result.getFieldValue("expiryYear");
let expiryMonth = result.getFieldValue("expiryMonth");
let expiryDay = result.getFieldValue("expiryDay");
if (parseInt(expiryYear) >= 60) {
expiryYear = "19" + expiryYear;
} else {
expiryYear = "20" + expiryYear;
}
parseResultInfo["Date of Expiry (YYYY-MM-DD)"] = expiryYear + "-" + expiryMonth + "-" + expiryDay;
return parseResultInfo;
}
Creating a Document Edge Editor
Use a div element to create a document edge editor.
<div class="container" id="document_editor">
<div>
<button onclick="edit()">Edit</button>
<button onclick="rectify()">Rectify</button>
<button onclick="save()">Save</button>
</div>
<div class="imageview" id="edit_view">
<img id="target_file" src="default.png" />
<canvas id="target_canvas" class="overlay"></canvas>
</div>
<div class="imageview" id="rectify_view">
<img id="rectified_image" src="default.png" />
</div>
</div>
The editor allows users to manually refine the detected document edges if the initial detection is imprecise. The coordinates of the document’s four points are stored in the globalPoints array. When the user clicks and drags a point, the coordinates are updated and the quad is redrawn.
function openEditor(image) {
let target_context = targetCanvas.getContext('2d');
targetCanvas.addEventListener("mousedown", (event) => updatePoint(event, target_context, targetCanvas));
targetCanvas.addEventListener("touchstart", (event) => updatePoint(event, target_context, targetCanvas));
drawQuad(target_context, targetCanvas);
targetFile.src = image;
}
function updatePoint(e, context, canvas) {
if (!globalPoints) {
return;
}
function getCoordinates(e) {
let rect = canvas.getBoundingClientRect();
let scaleX = canvas.clientWidth / canvas.width;
let scaleY = canvas.clientHeight / canvas.height;
let mouseX = e.clientX || e.touches[0].clientX;
let mouseY = e.clientX || e.touches[0].clientY;
if (scaleX < scaleY) {
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top - (canvas.clientHeight - canvas.height * scaleX) / 2;
mouseX = mouseX / scaleX;
mouseY = mouseY / scaleX;
}
else {
mouseX = e.clientX - rect.left - (canvas.clientWidth - canvas.width * scaleY) / 2;
mouseY = e.clientY - rect.top;
mouseX = mouseX / scaleY;
mouseY = mouseY / scaleY;
}
return { x: Math.round(mouseX), y: Math.round(mouseY) };
}
let delta = 10;
let coordinates = getCoordinates(e);
for (let i = 0; i < globalPoints.length; i++) {
if (Math.abs(globalPoints[i].x - coordinates.x) < delta && Math.abs(globalPoints[i].y - coordinates.y) < delta) {
canvas.addEventListener("mousemove", dragPoint);
canvas.addEventListener("mouseup", releasePoint);
canvas.addEventListener("touchmove", dragPoint);
canvas.addEventListener("touchend", releasePoint);
function dragPoint(e) {
coordinates = getCoordinates(e);
globalPoints[i].x = coordinates.x;
globalPoints[i].y = coordinates.y;
drawQuad(context, canvas);
}
function releasePoint() {
canvas.removeEventListener("mousemove", dragPoint);
canvas.removeEventListener("mouseup", releasePoint);
canvas.removeEventListener("touchmove", dragPoint);
canvas.removeEventListener("touchend", releasePoint);
}
break;
}
}
}
function drawQuad(context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.strokeStyle = "#00ff00";
context.lineWidth = 2;
for (let i = 0; i < globalPoints.length; i++) {
context.beginPath();
context.arc(globalPoints[i].x, globalPoints[i].y, 5, 0, 2 * Math.PI);
context.stroke();
}
context.beginPath();
context.moveTo(globalPoints[0].x, globalPoints[0].y);
context.lineTo(globalPoints[1].x, globalPoints[1].y);
context.lineTo(globalPoints[2].x, globalPoints[2].y);
context.lineTo(globalPoints[3].x, globalPoints[3].y);
context.lineTo(globalPoints[0].x, globalPoints[0].y);
context.stroke();
}
Scanning Barcodes, MRZ, and Documents from a Camera
-
Create a
divelement that will serve as the camera view, providing a visual output of the camera feed.<div class="container" id="camera_container"> <div> <select onchange="cameraChanged()" id="camera_source"> </select> <button onclick="scan()" id="scan_button">Start</button> <button onclick="capture()">Capture a document</button> <div id="videoview"> <div id="camera_view"></div> </div> <div class="row"> <div> <textarea id="scan_result"></textarea> </div> </div> </div> </div> -
Choose and open a camera.
async function cameraChanged() { if (cameras != null && cameras.length > 0) { let index = cameraSource.selectedIndex; await openCamera(cameraEnhancer, cameras[index]); } } async function openCamera(cameraEnhancer, cameraInfo) { if (!Dynamsoft) return; try { await cameraEnhancer.selectCamera(cameraInfo); cameraEnhancer.on("played", function () { resolution = cameraEnhancer.getResolution(); }); await cameraEnhancer.open(); } catch (ex) { console.error(ex); } } -
Start the scanner for detecting barcodes, MRZ (Machine Readable Zones), and documents from the camera feed. The results will be returned asynchronously via the
onCapturedResultReceivedevent handler.cvr.addResultReceiver({ onCapturedResultReceived: (result) => { showCameraResult(result); }, }); async function scan() { if (!isSDKReady) { alert("Please activate the SDK first."); return; } let selectedMode = document.querySelector('input[name="scanMode"]:checked').value; if (selectedMode == "mrz") { let scanRegion = { x: 10, y: 30, width: 80, height: 40, isMeasuredInPercentage: true }; cameraEnhancer.setScanRegion(scanRegion); } else { cameraEnhancer.setScanRegion(null); } if (!isDetecting) { scanButton.innerHTML = "Stop"; isDetecting = true; cvr.setInput(cameraEnhancer); if (selectedMode == "mrz") { await cvr.initSettings("./full.json"); cvr.startCapturing("ReadMRZ"); } else if (selectedMode == "barcode") { await cvr.resetSettings(); cvr.startCapturing("ReadBarcodes_Default"); } else if (selectedMode == "document") { await cvr.resetSettings(); let params = await cvr.getSimplifiedSettings("DetectDocumentBoundaries_Default"); params.outputOriginalImage = true; await cvr.updateSettings("DetectDocumentBoundaries_Default", params); cvr.startCapturing("DetectDocumentBoundaries_Default"); } } else { scanButton.innerHTML = "Scan"; isDetecting = false; await cvr.stopCapturing(); cameraView.clearAllInnerDrawingItems(); } } -
Display the detection results in real-time:
async function showCameraResult(result) { let selectedMode = document.querySelector('input[name="scanMode"]:checked').value; let items = result.items; let scan_result = document.getElementById('scan_result'); scan_result.innerHTML = ""; let txts = []; let type; if (selectedMode == "barcode") { type = Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE; } else if (selectedMode == "mrz") { type = Dynamsoft.Core.EnumCapturedResultItemType.CRIT_TEXT_LINE; } else if (selectedMode == "document") { type = Dynamsoft.Core.EnumCapturedResultItemType.CRIT_DETECTED_QUAD } if (items != null && items.length > 0) { for (var i = 0; i < items.length; ++i) { let item = items[i]; if (items[i].type === type) { txts.push(item.text); globalPoints = item.location.points; if (selectedMode == "barcode") { if (txts.length > 0) { scan_result.innerHTML += txts.join('\n') + '\n\n'; } else { scan_result.innerHTML += "Recognition Failed\n"; } } else if (selectedMode == "mrz") { if (txts.length > 0) { scan_result.innerHTML += txts.join('\n') + '\n\n'; parseResults = await parser.parse(item.text); scan_result.innerHTML += JSON.stringify(extractMrzInfo(parseResults)); } else { scan_result.innerHTML += "Recognition Failed\n"; } } } else if (items[i].type === Dynamsoft.Core.EnumCapturedResultItemType.CRIT_ORIGINAL_IMAGE) { if (selectedMode == "document") { if (isCaptured) { isCaptured = false; await scan(); targetCanvas.width = resolution.width; targetCanvas.height = resolution.height; openEditor(item.imageData.toCanvas().toDataURL()); } } } } } else { scan_result.innerHTML += "Nothing found\n"; } }
Testing the Web Scanner Application
Barcode Scanner

MRZ Scanner

Document Scanner
