Como ler um QR Code através do navegador utilizando JavaScript

Em certas situações, é necessário que nosso sistema leia um QR Code para obter informações de um produto ou item a ser processado. Utilizar um programa externo para essa tarefa pode ser trabalhoso. Uma alternativa mais prática é recorrer à API de Câmera do navegador junto com a biblioteca jsQR, permitindo a leitura e decodificação do QR Code de forma simples e eficiente.
Capturando imagens da Câmera
Para iniciar, é necessário capturar uma imagem pelo navegador. Para isso, utilizamos a interface MediaDevices
, que permite o acesso a dispositivos de mídia, como câmera e microfone. A captura é realizada por meio do método navigator.mediaDevices.getUserMedia
, que solicita ao usuário permissão para utilizar a mídia e retorna um MediaStream
contendo o conteúdo capturado. Esse método recebe um objeto de restrições (constraints
), especificando os tipos de mídia desejados, como vídeo e áudio, e retorna uma promise que resolve em um MediaStream
. Esse fluxo de mídia pode então ser atribuído a um elemento <video>
no HTML, permitindo que o usuário visualize a transmissão da câmera em tempo real.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Captura de Vídeo</title>
<script>
document.addEventListener('DOMContentLoaded', ev =>{
const cam = document.querySelector("#cam");
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
}).then( stream => {
cam.srcObject = stream;
})
});
</script>
</head>
<body>
<video autoplay id="cam" width="300" height="300"></video>
</body>
</html>
Se o dispositivo possuir mais de uma câmera, é possível listar todas elas utilizando o método navigator.mediaDevices.enumerateDevices()
. Esse método retorna um array de objetos MediaDeviceInfo
, contendo informações sobre cada dispositivo de mídia disponível. Entre os campos retornados, destacam-se: deviceId
(identificador do dispositivo), kind
(tipo do dispositivo, podendo ser audioinput
ou videoinput
), label
(descrição do dispositivo) e groupId
(identificador do grupo de dispositivos relacionados).
Vale ressaltar que o método enumerateDevices()
não solicita permissão para acessar os dispositivos. Portanto, é necessário chamar previamente a função getUserMedia()
, que solicita a autorização do usuário antes de listar os dispositivos disponíveis.
Para utilizar um dispositivo específico, basta passar seu deviceId
no objeto de restrições (constraints
) ao chamar o método getUserMedia()
.
{
video: {
deviceId: {exact: deviceId}
}
}
Outro aspecto importante é que, em dispositivos móveis, se um stream já estiver sendo exibido em um elemento <video>
, é necessário liberar o stream atual antes de iniciar um novo. Isso pode ser feito acessando todas as tracks do stream, iterando sobre elas e interrompendo-as com o método stop()
.
...
<script>
document.addEventListener('DOMContentLoaded', ev =>{
const devicesSelect = document.querySelector('#devices');
let currentStream = null;
function previewCam(deviceId){
if(deviceId == null) return;
const cam = document.querySelector('#cam');
if(currentStream){
currentStream.getTracks().forEach(track => track.stop());
}
navigator.mediaDevices.getUserMedia({
video: {
deviceId: {exact: deviceId}
}
}).then( stream =>{
currentStream = stream;
cam.srcObject = stream;
})
}
async function getDevices(){
try{
await navigator.mediaDevices.getUserMedia({ video: true });
const devices = await navigator.mediaDevices.enumerateDevices();
devices.forEach( d =>{
if(d.kind == 'videoinput'){
devicesSelect.innerHTML += `<option value="${d.deviceId}">${d.label}</option>`;
}
});
previewCam(devicesSelect[devicesSelect.selectedIndex].value)
}catch(e){
console.log("Erro ao acessar a camera");
}
}
devicesSelect.addEventListener('change', ev => {
previewCam( ev.target.value )
});
getDevices();
})
</script>
</head>
<body>
<select id="devices"></select>
<video autoplay id="cam" width="300" height="300"></video>
</body>
....
No código anterior, definimos duas funções: getDevices
, responsável por listar todos os dispositivos de vídeo em um elemento <select>
, e previewCam
, que exibe o stream do dispositivo selecionado em um elemento <video>
.
Na função getDevices
, na linha 27, chamamos o método getUserMedia
, ignorando seu retorno, apenas para solicitar permissão de uso da câmera. Em seguida, na linha 28, utilizamos o método enumerateDevices()
para obter a lista de dispositivos disponíveis. Iteramos sobre os objetos MediaDeviceInfo
com forEach
e, para cada item do tipo videoinput
, adicionamos um elemento <option>
ao <select>
, definindo o value
como o deviceId
e o texto exibido como o label
. Por fim, na linha 34, selecionamos o primeiro item do <select>
, obtemos seu value
(correspondente ao deviceId
) e o passamos para a função previewCam
.
Na função previewCam
, recebemos o deviceId
como parâmetro. Na linha 11, verificamos se há um stream ativo; caso positivo, obtemos todas as suas tracks, iteramos sobre elas e as interrompemos com o método stop()
. Na linha 15, chamamos o método getUserMedia()
, passando um objeto de configuração onde o atributo video
recebe o deviceId
. Quando a promise retorna, armazenamos o stream na variável currentStream
, permitindo fechá-lo posteriormente, se necessário, e o atribuimos ao elemento <video>
para exibição.
Pegando uma imagem da câmera
O próximo passo é capturar a imagem do stream de vídeo para enviá-la à biblioteca jsQR. Para isso, precisamos de um elemento <canvas>
onde vamos desenhar um frame do vídeo. O elemento <canvas>
pode ser oculto, se necessário.
Abaixo está um exemplo de código para capturar a imagem de um elemento de vídeo e desenhá-la em um <canvas>
ao clicar em um botão.
<script>
...
const btCapturar = document.querySelector('#capturar');
btCapturar.addEventListener('click', ev =>{
const cam = document.querySelector('#cam');
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext('2d');
canvas.width = cam.videoWidth;
canvas.height = cam.videoHeight;
ctx.drawImage(cam, 0, 0, canvas.width, canvas.height);
});
...
</scrip>
...
<select id="devices"></select>
<video autoplay id="cam" width="300" height="300"></video>
<button id="capturar">Capturar</button>
<canvas id="canvas" width="300" height="300"></canvas>
...
Finalmente lendo o QR Code
Agora que temos a imagem, podemos utilizar a biblioteca jsQR. Para instalá-la, basta usar o npm.
npm install jsqr --save
// ES6 import
import jsQR from "jsqr";
jsQR(...);
Ou você pode baixar o código de distribuição do GitHub, jsQR.js
, e incluí-lo diretamente via script.
<script src="jsQR.js"></script>
<script>
jsQR(...);
</script>
Agora, para decodificar a imagem e extrair o QR Code, basta obter o DataImage do <canvas>
utilizando o método ctx.getImageData
e chamar a função fornecida pela biblioteca jsQR, passando o DataImage
e as dimensões do canvas
. A função retornará o conteúdo do QR Code.
jsQR(imageData.data, imageData.width, imageData.height);
<script>
...
const btCapturar = document.querySelector('#capturar');
btCapturar.addEventListener('click', ev =>{
const cam = document.querySelector('#cam');
const canvas = document.querySelector("#canvas");
const qrcode = document.querySelector("#qrcode");
console.log(canvas);
const ctx = canvas.getContext('2d');
canvas.width = cam.videoWidth;
canvas.height = cam.videoHeight;
ctx.drawImage(cam, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0,0,canvas.width, canvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if(code){
qrcode.value = code.data;
}
});
...
</script>
..
<select id="devices"></select>
<video autoplay id="cam" width="300" height="300"></video>
<button id="capturar">Capturar</button>
<input type="text" id="qrcode">
<canvas style="display:none" id="canvas" width="300" height="300"></canvas>
Como você pode ver, ler um QR Code é bem simples, com a biblioteca jsQR fazendo todo o trabalho. Agora, basta adaptar a solução conforme suas necessidades. T++!