When building a barcode scanner capable of scanning multiple codes at once, making it draw a bounding box around each detected barcode significantly improves the user experience.
A bounding box is a rectangular shape that defines the location and size of an object or region of interest in a 2D or 3D space. It is commonly used in computer vision, image processing, and digital mapping.
In barcode scanning software, bounding boxes provide visual real-time feedback, signaling which codes are being recognized by the scanner and which aren’t, e.g., because they’re unreadable, too far away, or use unsupported symbologies.
To illustrate the difference, we’ll first create a barcode scanning interface without bounding boxes using the browser-based Barcode Detection API and scan some barcodes. Then we’ll update the code to add the bounding boxes and scan them again. Finally, we’ll adapt the code so each barcode’s value is shown above its bounding box, which makes the feature even more user-friendly.
Creating the barcode scanning interface
To get our barcode scanner up and running, we need to build a small web app using HTML, CSS, and JavaScript. We’ll then run it in a browser compatible with the Barcode Detection API. At the time of writing, the API’s browser support is as follows:
Chrome Android | ✅ fully supported |
Opera Android | ✅ fully supported |
WebView Android | ✅ fully supported |
Samsung Internet | ✅ fully supported |
Chrome | ➖ partly supported |
Edge | ➖ partly supported |
Opera | ➖ partly supported |
Firefox | ❌ not supported |
Firefox Android | ❌ not supported |
Safari | ❌ not supported |
Safari iOS | ❌ not supported |
WebView iOS | ❌ not supported |
Let’s start by creating a simple index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Barcode Scanner</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Web Barcode Scanner</h1>
<div class="video-container">
<video id="video" autoplay playsinline></video>
</div>
<div id="result">Scanned Barcode: <span id="barcodeResult">None</span></div>
</div>
<script src="script.js"></script>
</body>
</html>
Next, we’re going to style it with a styles.css file:
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.container {
text-align: center;
}
h1 {
color: #333;
}
.video-container {
width: 100%;
max-width: 640px;
margin: 20px auto;
}
#video {
width: 100%;
height: auto;
border: 2px solid #333;
}
#result {
margin-top: 20px;
font-size: 18px;
}
#barcodeResult {
font-weight: bold;
color: #007bff;
}
And finally, let’s implement the actual barcode scanning logic in a script.js file:
document.addEventListener('DOMContentLoaded', () => {
const video = document.getElementById('video');
const resultSpan = document.getElementById('barcodeResult');
if ('BarcodeDetector' in window) {
const barcodeDetector = new BarcodeDetector();
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } })
.then(stream => {
video.srcObject = stream;
video.onloadedmetadata = () => {
video.play();
scanBarcodes();
};
})
.catch(err => {
console.error("Error accessing the camera:", err);
});
function scanBarcodes() {
barcodeDetector.detect(video)
.then(barcodes => {
if (barcodes.length > 0) {
resultSpan.textContent = barcodes[0].rawValue;
} else {
resultSpan.textContent = "None";
}
requestAnimationFrame(scanBarcodes);
})
.catch(err => {
console.error("Barcode detection error:", err);
requestAnimationFrame(scanBarcodes);
});
}
} else {
console.error('Barcode Detection API is not supported by this browser');
resultSpan.textContent = "Barcode Detection not supported";
}
});
The scanBarcodes
function uses the barcodeDetector.detect(video)
method to analyze the video feed. If barcodes are detected, the function updates the barcodeResult
element with the value of the first detected barcode. If no barcode is detected, it updates the result to “None”.
After detecting barcodes, the function uses requestAnimationFrame(scanBarcodes)
to recursively call itself, creating a continuous loop. This allows the barcode detection process to keep running in real time as the video feed updates.
Let’s try it out!

Without bounding boxes, it’s hard to know which codes are being recognized and which aren’t.
To improve this, we’ll update the code to make it draw bounding boxes on top of each recognized barcode and try again.
Implementing the bounding boxes
First, apply the following changes to script.js:
- Introduce a
drawBoundingBox
function to draw bounding boxes around detected barcodes. - Clear previous bounding boxes with a
clearBoundingBoxes
function to keep the display updated with the latest detection. - Iterate through all detected barcodes to create and display a bounding box for each.
We’ll also need to introduce a .video-container
element to serve as a parent container for the video feed and the overlayed bounding boxes, since they must be visually aligned. By wrapping the video feed in a .video-container
, we ensure both the video and the bounding boxes share the same coordinate system, making it easier to position the bounding boxes accurately over the detected barcodes.
Afterward, your script.js should look like this:
document.addEventListener('DOMContentLoaded', () => {
const video = document.getElementById('video');
const resultSpan = document.getElementById('barcodeResult');
const container = document.querySelector('.video-container');
if ('BarcodeDetector' in window) {
const barcodeDetector = new BarcodeDetector();
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } })
.then(stream => {
video.srcObject = stream;
video.onloadedmetadata = () => {
video.play();
scanBarcodes();
};
})
.catch(err => {
console.error("Error accessing the camera:", err);
});
function scanBarcodes() {
barcodeDetector.detect(video)
.then(barcodes => {
clearBoundingBoxes();
if (barcodes.length > 0) {
resultSpan.textContent = barcodes[0].rawValue;
barcodes.forEach(barcode => {
drawBoundingBox(barcode.boundingBox);
});
} else {
resultSpan.textContent = "None";
}
requestAnimationFrame(scanBarcodes);
})
.catch(err => {
console.error("Barcode detection error:", err);
requestAnimationFrame(scanBarcodes);
});
}
function drawBoundingBox(box) {
const boundingBox = document.createElement('div');
boundingBox.className = 'bounding-box';
boundingBox.style.left = `${box.x}px`;
boundingBox.style.top = `${box.y}px`;
boundingBox.style.width = `${box.width}px`;
boundingBox.style.height = `${box.height}px`;
container.appendChild(boundingBox);
}
function clearBoundingBoxes() {
const boxes = document.querySelectorAll('.bounding-box');
boxes.forEach(box => box.remove());
}
} else {
console.error('Barcode Detection API is not supported by this browser');
resultSpan.textContent = "Barcode Detection not supported";
}
});
Now apply the following changes to styles.css:
- Add a
.bounding-box
class withposition: absolute
to ensure the element is positioned relative to its closest positioned ancestor,border: 2px solid #ff0000
to add a red border to the box, andbox-sizing: border-box
to ensure padding and borders are included within the element’s width and height. - Update
.video-container
with a new property positionrelative
to allow the absolutely positioned element.bounding-box
to be placed relative to.video-container
.
The resulting styles.css will look like this:
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.container {
text-align: center;
}
h1 {
color: #333;
}
.video-container {
position: relative;
width: 100%;
max-width: 640px;
margin: 20px auto;
}
#video {
width: 100%;
height: auto;
border: 2px solid #333;
}
#result {
margin-top: 20px;
font-size: 18px;
}
#barcodeResult {
font-weight: bold;
color: #007bff;
}
.bounding-box {
position: absolute;
border: 2px solid #ff0000;
box-sizing: border-box;
}
With the bounding boxes implemented, let’s try scanning some barcodes again!

This setup makes it much easier to tell which barcodes are being recognized, which improves the app’s user experience. However, since we’re scanning multiple barcodes at once, outputting only one result at a time is not enough. So let’s improve our app even further and display each scanned barcode’s value at the top of its bounding box!
Displaying barcode values above the bounding boxes
First, apply the following changes to script.js:
- Remove the
resultSpan
that we used to display the first detected barcode’s value. - Enhance the
drawBoundingBox
function by adding an extra element forbounding-box-label
to display the barcode value directly on the video. The label is positioned above the bounding box using thetop
style with a negative offset (box.y - 20
). The label text comes from the barcode’srawValue
. - Update the
clearBoundingBoxes
function by removing bothbounding-box
andbounding-box-label
elements, ensuring no lingering labels.
The resulting script.js looks like this:
document.addEventListener('DOMContentLoaded', () => {
const video = document.getElementById('video');
const container = document.querySelector('.video-container');
if ('BarcodeDetector' in window) {
const barcodeDetector = new BarcodeDetector();
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } })
.then(stream => {
video.srcObject = stream;
video.onloadedmetadata = () => {
video.play();
scanBarcodes();
};
})
.catch(err => {
console.error("Error accessing the camera:", err);
});
function scanBarcodes() {
barcodeDetector.detect(video)
.then(barcodes => {
clearBoundingBoxes();
if (barcodes.length > 0) {
barcodes.forEach(barcode => {
drawBoundingBox(barcode.boundingBox, barcode.rawValue);
});
}
requestAnimationFrame(scanBarcodes);
})
.catch(err => {
console.error("Barcode detection error:", err);
requestAnimationFrame(scanBarcodes);
});
}
function drawBoundingBox(box, value) {
const boundingBox = document.createElement('div');
boundingBox.className = 'bounding-box';
boundingBox.style.left = `${box.x}px`;
boundingBox.style.top = `${box.y}px`;
boundingBox.style.width = `${box.width}px`;
boundingBox.style.height = `${box.height}px`;
const label = document.createElement('div');
label.className = 'bounding-box-label';
label.textContent = value;
label.style.left = `${box.x}px`;
label.style.top = `${box.y - 20}px`; // Position the label above the bounding box
container.appendChild(boundingBox);
container.appendChild(label);
}
function clearBoundingBoxes() {
const boxes = document.querySelectorAll('.bounding-box, .bounding-box-label');
boxes.forEach(box => box.remove());
}
} else {
console.error('Barcode Detection API is not supported by this browser');
}
});
Next, in styles.css, add the .bounding-box-label
class to style the labels displayed above the bounding boxes:
position: absolute
ensures the label can be placed anywhere relative to its containing element.background-color: rgba(255, 255, 255, 0.8)
adds a translucent white background to improve the label’s readability over video content.padding: 2px 5px
adds padding inside the label for better spacing of the text.border: 1px solid #333
adds a thin, dark border around the label to make it visually distinct.font-size: 14px
sets the font size to ensure the label text is legible without being intrusive.color: #333
uses a dark gray color for the text, ensuring good contrast with the white background.box-sizing: border-box
includes the element’s padding and border in its total width and height.
Your styles.css should now look like this:
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.container {
text-align: center;
}
h1 {
color: #333;
}
.video-container {
position: relative;
width: 100%;
max-width: 640px;
margin: 20px auto;
}
#video {
width: 100%;
height: auto;
border: 2px solid #333;
}
#result {
margin-top: 20px;
font-size: 18px;
}
#barcodeResult {
font-weight: bold;
color: #007bff;
}
.bounding-box {
position: absolute;
border: 2px solid #ff0000;
box-sizing: border-box;
}
.bounding-box-label {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 2px 5px;
border: 1px solid #333;
font-size: 14px;
color: #333;
box-sizing: border-box;
}
And finally, in index.html, remove the div
displaying the scanning result below the scanning interface:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Barcode Scanner</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Web Barcode Scanner</h1>
<div class="video-container">
<video id="video" autoplay playsinline></video>
<div class="viewfinder"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Now, let’s try scanning those barcodes again!

Our barcode scanner is now much more user-friendly, since it’s immediately apparent which barcodes have been detected and what values they contain.
While cobbling together a scanning interface can be fun, ready-made solutions are available for these kinds of use cases. The Scanbot Barcode Scanner SDK comes with a customizable AR overlay that highlights all detected barcodes in the viewfinder and can display additional information related to their encoded values.
How the Scanbot SDK handles bounding boxes for highlighting barcodes
The Scanbot SDK was designed for enterprise use and includes many quality-of-life features, including a togglable bounding box feature.

Tapping on a highlighted barcode brings up more details, which can even include information about a product associated with that code’s value – thanks to our SDK’s barcode mapping feature.
With the Scanbot SDK’s ready-to-use UI components, you can set up a barcode scanning interface like this in minutes while having complete control over its look and feel. Its in-depth documentation and free integration support cater to the needs of developers and ensure a short time to roll-out.
The Scanbot Barcode Scanner SDK is available for Android, iOS, Web, Windows, and Linux and supports various cross-platform frameworks, including React Native, Flutter, and .NET MAUI.
Experience the SDK yourself by trying our free demo apps or running our example apps for various platforms. If you’d like to test the SDK in your app, generate a free 7-day trial license to start integration immediately.