Skip to main content

How to Create a QR Code Reader for Your Mobile Website

Share:

Free JavaScript Book!

Write powerful, clean and maintainable JavaScript.

RRP $11.95

The barcode and QR code have modernized our shopping and searching experience. Customers carrying smartphones can now pick up a product anywhere in the world, scan its barcode or its QR code using one of the many free phone apps and find out its lowest price as well as where it can be bought.

Screenshots of apps with barcode scanner buttons

Companies like Walmart and Amazon have embraced this technique to draw customers to their online and offline stores using their phone app. Other companies like Fedex and UPS allow customers to scan the codes on packages using a phone app, instead of needing to manually type in long lists of characters.

If the users of your mobile website have a need to type in long codes like activation codes, or they like to look up specific products on your website based on a model number printed in a magazine or advertisement, then you too can take advantage of QR codes to eliminate the frustration of tiny keyboards and spare them the need to double check for errors.

QR Code Scanning with Your Mobile Website

You don’t need a native phone app to scan QR codes. It’s quite simple to create your own QR code reader. Your website running on a smartphone equipped with a camera and running a little JavaScript can do the same trick.

Here’s a demo of a QR code scanner that works not only on Mobile but also in most modern devices. All you need is a camera and a QR code to scan.

If you don’t have a QR code handy, here’s one that shows the first eight digits of Pi.

Test the QR Code Reader: QR code encoding the value of Pi

Creating the QR Code Reader

Our QR code reader will need some HTML and JavaScript but most importantly, a JavaScript library capable of interpreting the QR code.

We’re not going to build that ourselves, because there are some great libraries out there doing this for us, so we don’t need to reinvent the wheel for our current purposes.

Let’s begin by creating an index.html file.

Adding the HTML

We’ll need some very simple HTML for this project. Add the following to your body tag:

<div id="container">
    <h1>QR Code Scanner</h1>

    <a id="btn-scan-qr">
        <img src="https://uploads.sitepoint.com/wp-content/uploads/2017/07/1499401426qr_icon.svg">
    <a/>

    <canvas hidden="" id="qr-canvas"></canvas>

    <div id="qr-result" hidden="">
        <b>Data:</b> <span id="outputData"></span>
    </div>
</div>

<script src="./src/qrCodeScanner.js"></script>

As you can see, we have a wrapper container with a title, the QR icon image wrapped in an a tag, a canvas and a div where we’ll show the result of the scan.

Outside the container div we’re including the qrCodeScanner.js file. We’ll create it later, but first we’ll improve the look of our app.

Adding Styles

Add the stylesheet to the head of our HTML:

<link rel="stylesheet" href="src/styles.css" />

Now we want to create the style.css file within the src folder. We just want some basic styles for this sample app. Add the following to your css file:

html {
  height: 100%;
}

body {
  font-family: sans-serif;
  padding: 0 10px;
  height: 100%;
  background: black;
  margin: 0;
}

h1 {
  color: white;
  margin: 0;
  padding: 15px;
}

#container {
  text-align: center;
  margin: 0;
}

#qr-canvas {
  margin: auto;
  width: calc(100% - 20px);
  max-width: 400px;
}

#btn-scan-qr {
  cursor: pointer;
}

#btn-scan-qr img {
  height: 10em;
  padding: 15px;
  margin: 15px;
  background: white;
}

#qr-result {
  font-size: 1.2em;
  margin: 20px auto;
  padding: 20px;
  max-width: 700px;
  background-color: white;
}

Nothing fancy at all. We’ll leave everything centered with a big QR button in the middle and the result underneath. We’re using black and white like the QR codes.

Including the Dependent JavaScript Libraries

The secret to reading QR codes is math, and the substitute for math is open-source libraries. To read QR codes, we’ll be using the JavaScript port of the Java-based image processing library written by ZXing. The JavaScript version was ported by Lazar Laszlo.

Because the JavaScript library consists of 17 files, we’ve taken the liberty of merging them into one file, wrapping the code in an anonymous function to prevent pollution of the global namespace and putting the file through Google Closure’s minifier to make the file size smaller.

Some Minor Tweaks to the Library

In order to make the library more adaptable, we’ve added a few minor changes to the library’s output function to differentiate between a success response and an error response.

Two important changes made were in qrcode.js, to these two lines:

qrcode.result = "error decoding QR Code";
//...
qrcode.callback("Failed to load the image");

These strings have been replaced by Error objects:

qrcode.result = Error("error decoding QR Code");
//...
qrcode.callback(Error("Failed to load the image"));

Now I can detect in my callback function whether an error occurred, just by checking if the callback payload is an instance of Error or not.

Those changes can be found in this fork of the library.

Adding the Script Tag

To use the library in our QR code reader, we first need to include it in our HTML using a regular script tag:

<script src="https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js">
</script>

Treating It as an App

Something we’ll need to do is tell mobile browsers that we don’t want to scale this site in portrait mode. This can be achieved by adding the following meta tag within the head element:

<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>

Adding the JavScript

Now we need to create the qrCodeReader.js file in the src folder, which should be at the same level as our HTML file.

Let’s add some code to our new file:

const qrcode = window.qrcode;

const video = document.createElement("video");
const canvasElement = document.getElementById("qr-canvas");
const canvas = canvasElement.getContext("2d");

const qrResult = document.getElementById("qr-result");
const outputData = document.getElementById("outputData");
const btnScanQR = document.getElementById("btn-scan-qr");

let scanning = false;

At the top of this bit of code we get the qrcode object from the window and assign it to a constant for convenience. We’re also creating a video element that we’ll use to handle the images coming from the camera.

Then we get the canvas element and we use it to assign the 2d context to a constant. We’ll need this to draw the images coming from our camera.

Then we get the relevant elements to show the results and interact with the app and, at the bottom, we declare the scanning variable, to keep the status of our scanner.

Next we’ll set the callback for our QR code reader. Add the following at the bottom of the file:

qrcode.callback = (res) => {
  if (res) {
    outputData.innerText = res;
    scanning = false;

    video.srcObject.getTracks().forEach(track => {
      track.stop();
    });

    qrResult.hidden = false;
    btnScanQR.hidden = false;
    canvasElement.hidden = true;
  }
};

Here we’re assigning the callback function of the qrcode object. This will be called by the library when it detects a QR code. It provides the res parameter containing the result of the scan, so we’re assigning that to the innerText property of the outputData element.

There are four other things going on here. First, we’re setting the scanning variable to false, since we don’t want to scan anymore after we’ve already decoded our QR code.

Then, we’re getting all tracks from the stream inside the srcObjec property of the video element and stopping them one by one. This is how we stop streaming the user’s camera.

Right after that, we’re making sure we display the qrResult element and the btnScanQR element so that the user can see the result and trigger another scan. Finally, we hide the canvasElement, since we don’t need it anymore.

This is all we need to handle the scanner response.

Now we need to access the camera feed and set up a loop to draw the images in our canvas every frame. We also need another loop to scan for QR codes every x milliseconds.

Scanning every frame would be a waste of resources, so we’re better off handling that in a separate loop where we can control the frequency in which we run the algorithm.

We’ll do this in the onclick handler of the btnScanQR element:

btnScanQR.onclick = () =>
  navigator.mediaDevices
    .getUserMedia({ video: { facingMode: "environment" } })
    .then(function(stream) {
      scanning = true;
      qrResult.hidden = true;
      btnScanQR.hidden = true;
      canvasElement.hidden = false;
      video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
      video.srcObject = stream;
      video.play();
      tick();
      scan();
    });
};

OK, let’s go through this. We’re calling the getUserMedia function from the mediaDevices object, which is part of the navigator object. This will make the browser ask the user for permission to use their camera.

The getUserMedia function takes an object as a parameter, to which we’re passing the video object with the facingMode set to "environment". If the user is using a mobile device, this will attempt to get the camera on the back. It returns a promise that, when resolved, provides a stream we can assign to the srcObject of the video element we created. Then we’re setting the "playsinline" attribute to true, which will prevent iOS safari from going into fullscreen.

At this point, we can play() the video but, of course, this is not enough. We need to draw the stream every frame, so we’re calling the tick function for that purpose and then the scan function to trigger the algorithm.

Let’s define the tick function:

function tick() {
  canvasElement.height = video.videoHeight;
  canvasElement.width = video.videoWidth;
  canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);

  scanning && requestAnimationFrame(tick);
}

This is a classic frame-by-frame loop. We’re setting the hight and width of the canvasElement to the dimensions of the video. Then we draw the video to the canvas and at the bottom we use requestAnimationFrame and pass in the tick function so that it will be called again when the browser draws the next frame. We’re doing this conditionally to the scanning variable being true.

Now let’s define the scan function:

function scan() {
  try {
    qrcode.decode();
  } catch (e) {
    setTimeout(scan, 300);
  }
}

Now, this is quite simple. We run the decode function from the qrcode library, which will look for a canvas with an ID of "qr-canvas" and scan its contents. If we can’t find anything, the error we defined will be caught and we’ll call a setTimeout to scan in 300 milliseconds. You can set this to something else to see what happens. The more you wait for the next scan, the slower it’ll be. The less you wait, the more you’ll be demanding from the user’s device, so be mindful. Try to look for a sweet spot.

That’s all we need! Now let’s try the app.

See the QR Code Reader in Action

Here’s the working project in codesandbox. Click on the QR button and show the camera some QR code to scan. Hold it in place for an instant and you’ll get your result. You’ll be surprised at how fast and smooth it is.

Conclusion

So there we have it, your very own QR code reader for your mobile website. You can also use this from any platform, which makes it super dynamic and brings a lot of value to your customers.

QR codes have been around for many years, and the image processing code written by ZXing was first ported to JavaScript almost nine years ago. It has stood the test of time so well that it still remains one of the fastest — if not the fastest — option out there for the Web. It’s also free an open source, which makes it even better.

We hope you have fun coming up with something amazing!

Downloadable Assets

Dmitri Lau is a freelance Ajax developer with a penchant for statistical analysis. When not improving a meta template engine's relative response yield, yawning uninterruptedly every night has become a norm which he hopes will soon be over when his Hong Kong based startup picks up.

Aside from being a software developer, I am also a massage therapist, an enthusiastic musician, and a hobbyist fiction writer. I love traveling, watching good quality TV shows and, of course, playing video games.

New books out now!

Learn how Git works, and how to use it to streamline your workflow!


Google, Netflix and ILM are Python users. Maybe you should too?