Skip to content

Instantly share code, notes, and snippets.

@bobvanderlinden
Last active October 28, 2022 20:41
Show Gist options
  • Save bobvanderlinden/a58f39e44dba53e17cd2e2ec4774af08 to your computer and use it in GitHub Desktop.
Save bobvanderlinden/a58f39e44dba53e17cd2e2ec4774af08 to your computer and use it in GitHub Desktop.
Simple QR-Code scanner based on jsqrcode
<html>
<head>
<script src="https://webqr.com/llqrcode.js"></script>
<script src="jsqrcode-camera.js"></script>
</head>
<body>
<div id="qrcodescanner"></div>
<div id="message"></div>
</body>
<script>
window.onload = function() {
QRCodeScanner({
element: document.getElementById('qrcodescanner'),
width: 400,
height: 300,
onScanSuccess: function(result) {
console.log('Scan Success', result);
document.getElementById('message').textContent = result;
},
onScanError: function(error) {
console.log('Scan Error', error);
document.getElementById('message').textContent = 'No QR Code found';
}
});
}
</script>
</html>
function QRCodeScanner(options) {
const rootElement = options.element;
const width = options.width;
const height = options.height;
const onScanSuccess = options.onScanSuccess;
const onScanError = options.onScanError;
const scanInterval = options.scanInterval || 100;
let stream = undefined;
let deviceId = undefined;
let availableVideoInputs = undefined;
function c(tagName, attributes, children) {
const element = document.createElement(tagName);
Object.assign(element, attributes);
replaceChildren(element, children);
return element;
}
function replaceChildren(parent, children) {
parent.innerHTML = '';
appendChildren(parent, children);
}
function appendChildren(parent, children) {
(children || []).forEach(function(child) {
if (typeof child === 'string') {
child = document.createTextNode(child);
}
parent.appendChild(child);
});
}
function activatePanel(panel) {
panel.parentNode.childNodes.forEach(function(node) {
node.style.display = panel === node ? 'block' : 'none';
});
}
const loadingPanel = c('div', {
className: 'loading panel'
}, []);
Object.assign(loadingPanel.style, {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0
});
const errorPanel = c('div', {
className: 'error panel'
}, []);
Object.assign(errorPanel.style, {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0
});
const switchVideoInputButton = c('button', {
className: 'switch button',
onclick: handleSwitchVideoInputButtonClick
});
const controlsOverlayElement = c('div', {
className: 'controls'
}, [
switchVideoInputButton
]);
Object.assign(controlsOverlayElement.style, {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0
});
const videoElement = c('video', {
autoplay: true
});
Object.assign(videoElement.style, {
width: width + 'px',
height: height + 'px'
});
const videoPanel = c('div', {
className: 'video panel'
}, [
videoElement,
controlsOverlayElement
]);
Object.assign(videoPanel.style, {
position: 'relative',
left: 0,
right: 0,
top: 0,
bottom: 0
});
const URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
function initializeUserMedia() {
if (!navigator.getUserMedia) {
errorPanel.textContent = 'Camera not supported';
activatePanel(errorPanel);
}
navigator.getUserMedia({
video: {
facingMode: 'environment',
deviceId: deviceId
? { exact: deviceId }
: undefined
}
}, handleUserMediaSuccess, handleUserMediaError);
}
function handleUserMediaSuccess(stream) {
activatePanel(videoPanel);
if (availableVideoInputs === undefined) {
retrieveVideoInputs();
}
videoElement.srcObject = stream;
scheduleScan();
}
function retrieveVideoInputs() {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
switchVideoInputButton.style.display = 'none';
}
navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
availableVideoInputs = devices
.filter(function(device) {
return device.kind === 'videoinput';
});
switchVideoInputButton.style.display = availableVideoInputs.length < 2
? 'none'
: 'block';
});
}
function handleUserMediaError(error) {
errorPanel.textContent = 'Error activating camera';
activatePanel(errorPanel);
}
function handleSwitchVideoInputButtonClick() {
let currentAvailableVideoInputIndex = -1;
availableVideoInputs.forEach(function(videoinput, index) {
if (videoinput.deviceId === deviceId) {
currentAvailableVideoInputIndex = index;
}
});
const newAvailableVideoInputIndex = (currentAvailableVideoInputIndex + 1) % availableVideoInputs.length;
deviceId = availableVideoInputs[newAvailableVideoInputIndex].deviceId;
initializeUserMedia();
}
// Scanning QR code from camera at set interval.
// We reuse a premade canvas to copy the camera image to
// and convert the canvas to an ObjectUrl to pass it to jsqrcode.
var scanTimeout;
const canvas = c('canvas', {
width: width,
height: height
});
const context = canvas.getContext('2d');
function scheduleScan() {
clearTimeout(scanTimeout);
scanTimeout = setTimeout(scan, scanInterval);
}
function stopScan() {
clearTimeout(scanTimeout);
scanTimeout = undefined;
}
qrcode.callback = handleScanResult;
function handleScanResult(result) {
onScanSuccess(result);
}
function scan() {
if (!videoElement.parentNode) {
// Stop scanning when videoElement was detached.
return;
}
context.drawImage(videoElement, 0, 0, width, height);
// We set these two, so that jsqrcode won't
// look for an qrcode element using getElementById.
qrcode.canvas_qr2 = canvas;
qrcode.qrcontext2 = context;
try {
qrcode.decode();
} catch(e) {
// jsqrcode throws an error each time an qrcode was not found.
onScanError(e);
}
scheduleScan();
}
// Initialization of QRCodeScanner
Object.assign(rootElement.style, {
position: 'relative',
width: width + 'px',
height: height + 'px',
backgroundColor: 'black'
});
appendChildren(rootElement, [
errorPanel,
loadingPanel,
videoPanel
]);
activatePanel(loadingPanel);
initializeUserMedia();
return {
stop: stopScan
}
}
@jcrick69
Copy link

jcrick69 commented Feb 7, 2018

Hi, how do you get this code to recognise it has a qr code and then process it to give you the data for instance in a text box...?
Thanks, Jerry

@Schmido86
Copy link

Hi. I think there is an issue with iOS devices, isn't it? I can't solve it. In my case on iOS Devices the scanner doesn't work. I makes a black picture by opening the scanner. On other devices it works very well. Maybe you can solve it?

@bobvanderlinden
Copy link
Author

I unfortunately do not have an iOS device to test this. Is it a Safari problem in general that also exists on OSX?

@Schmido86
Copy link

Schmido86 commented Oct 17, 2021

the problem exists in safari or chrome as well.

@bobvanderlinden
Copy link
Author

bobvanderlinden commented Oct 17, 2021

It should be fixed now. URL.createObjectURL was deprecated for usage of MediaStreams. Now uses video.srcObject = stream;.

@Schmido86
Copy link

Now it directly makes a picture. I can't position the camera over a qr-code.

@matteoferrux
Copy link

Hi guys ! The issue with iOS always exists. Do you have a solution ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment