'Angular 6 + jsQR - How to capture QR code on the first success?
I use jsQR (https://www.npmjs.com/package/jsqr) with my Angular app to decode QR. Generally I copy the code from jsQR into my app, and it works right away. However, jsQR only reads when I hold a QR code in webcam area, when moving the code away, the previous result is not captured. So I want to capture the first QR code if success and bind it into a form to submit the result. How can I do it properly?
Here is my code so far:
ngOnInit() {
this.getQR();
}
getQR() {
const video = document.createElement('video');
const canvasElement: any = document.getElementById('canvas');
const canvas = canvasElement.getContext('2d');
const outputContainer = document.getElementById('output');
const outputMessage = document.getElementById('outputMessage');
const outputData = document.getElementById('outputData');
function drawLine(begin, end, color) {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
}
// Use facingMode: environment to attemt to get the front camera on phones
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }).then(function(stream) {
video.srcObject = stream;
// video.setAttribute('playsinline', true); // required to tell iOS safari we don't want fullscreen
video.play();
requestAnimationFrame(tick);
});
function tick() {
if (video.readyState === video.HAVE_ENOUGH_DATA) {
canvasElement.hidden = false;
outputContainer.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#009688');
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#009688');
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#009688');
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#009688');
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
outputData.innerText = code.data;
} else {
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
}
requestAnimationFrame(tick);
}
}
Solution 1:[1]
I have wrapped javascript pure code into typescript; this is my template:
<canvas id="scan-canvas"></canvas>
<div id="output">
<div id="outputMessage">No QR code detected.</div>
<div hidden=""><b>Data:</b> <span id="outputData">{{ qrcodeDetected }}</span></div>
</div>
Here the TS controller:
ngOnInit() {
this.canvasElement = <HTMLCanvasElement> document.getElementById('scan-canvas');
this.canvasContext = this.canvasElement.getContext('2d');
this.outputContainer = <HTMLDivElement>document.getElementById('output');
this.outputMessage = <HTMLDivElement>document.getElementById('outputMessage');
this.outputData = <HTMLDivElement>document.getElementById('outputData');
this.video = <HTMLVideoElement>document.createElement('video');
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }).then(async (stream: MediaStream) => {
this.video.srcObject = stream;
this.video.setAttribute('playsinline', 'true'); // required to tell iOS safari we don't want fullscreen
await this.video.play();
requestAnimationFrame(this.tick.bind(this));
});
}
drawLine(begin, end, color): void {
this.canvasContext.beginPath();
this.canvasContext.moveTo(begin.x, begin.y);
this.canvasContext.lineTo(end.x, end.y);
this.canvasContext.lineWidth = 4;
this.canvasContext.strokeStyle = color;
this.canvasContext.stroke();
}
tick(): void {
if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
this.canvasElement.hidden = false;
this.outputContainer.hidden = false;
this.canvasElement.height = this.video.videoHeight;
this.canvasElement.width = this.video.videoWidth;
this.canvasContext.drawImage(this.video, 0, 0, this.canvasElement.width, this.canvasElement.height);
const imageData: ImageData = this.canvasContext.getImageData(0, 0, this.canvasElement.width, this.canvasElement.height);
const code: QRCode = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
this.drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#FF3B58');
this.drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#FF3B58');
this.drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#FF3B58');
this.drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#FF3B58');
this.outputMessage.hidden = true;
this.outputData.parentElement.hidden = false;
this.qrcodeDetected = code.data;
} else {
this.outputMessage.hidden = false;
this.outputData.parentElement.hidden = true;
}
}
requestAnimationFrame(this.tick.bind(this));
}
Solution 2:[2]
To completely answer this question, Fabio Mercorillo's answer works great. Only missing thing to have a completely working example is adding the variable declarations to the component:
...
import jsQR, { QRCode } from 'jsqr';
...
export class MyComponent implements OnInit {
canvasElement: HTMLCanvasElement;
canvasContext: CanvasRenderingContext2D;
outputContainer: HTMLDivElement;
outputMessage: HTMLDivElement;
outputData: HTMLDivElement;
video: HTMLVideoElement;
qrcodeDetected: string;
constructor() { }
...
To answer the initial question, the last scanned code is saved to the qrcodeDetected: string
variable, the result is simply css-hidden by the else statement of the next scan/tick this.outputData.parentElement.hidden = true;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Patrick |
Solution 2 | denu5 |