Background & Context
Quick Response (QR) codes are a powerful tool. A QR code is a type of barcode that stores information as a series of pixels in a square-shaped grid that can be read easily by a digital device such a smartphone through its camera. QR codes are frequently used to track information about products in a supply chain and often used in marketing and advertising campaigns. The most common use for QR codes is to embed a url - giving you the ability to share a link without the tedious task of having to manually type it in a url bar correctly.
I recently had to build a token redemption feature onto a web application my team maintains. A direct url with a GUID seemed to be the best solution to go with. GUIDs are very long and to simplify the redemption process, we decided to distribute the tokens using QR codes. We needed to generate hundreds of tokens. To avoid mistakes and the tedious process of creating cards manually using wordπ, I decided to automate this. JavaScript is currently the hammer I hit all quick problems with so I looked for a web-based solution.
The solution to problem
We need to have a card that has a QR code in the middle and some accompanying text to say "congratulations" and provide instructions on how to redeem the code. We understood that not everyone would have a phone that's capable of scanning QR codes and some people might just not know how to. To address this concern, we build a simple input box that allowed people to enter the guid manually should they choose to do so. This added the requirement of printing the GUID onto the card that we distribute.
This means that we have to do three things to solve this problem:
- Generate the QR code
- Generate text around the QR Code which includes the Guid
- Download the resultant "card" in an acceptable format
Solution - Part 1: Generate QR Code
The core of the solution is automatically generating the QR code. My favourite thing about web-development is the collaborative spirit of developers in the space. As the joke goes: "just search for what you need and suffix it with 'js' and chances are that you'll find a library that does exactly that."
I went through a few npm packages before finding the QR Code Styling package which is simple to understand and easy to get started with.
You can include the package using unpkg which is a fast, global content delivery network (cdn) for everything on npm. You can include the script using the following line in the head of your html page:
javascript
<script type="text/javascript" src="https://unpkg.com/qr-code-styling@1.5.0/lib/qr-code-styling.js"></script>
Once included, you can create a new instance of a QR code using the new QRCodeStyling constructor which takes in an 'options' object as the parameter. The most basic properties of this 'options' parameter include the dimension of the code expressed as width and height, the data you want the code to represent - a url in our case, a url with the image you want at the center of the QR Code, and options configiuring the look and feel of the code like how rounded you want the edges of the code to be. An example code as adapted from the qr-code-styling docs is shown below:
javascript
<script type="text/javascript">
const qrCode = new QRCodeStyling({
width: 300,
height: 300,
type: "svg",
data: "https://dev.to/luckynkosi/",
image: "https://d2fltix0v2e0sb.cloudfront.net/dev-rainbow.svg",
dotsOptions: {
color: "#4267b2",
type: "rounded"
},
backgroundOptions: {
color: "#e9ebee",
},
imageOptions: {
crossOrigin: "anonymous",
margin: 20
}
});
//place it on the screen
qrCode.append(document.getElementById("canvas"));
//download the generate image of the QR code
qrCode.download({ name: "qr", extension: "svg" });
</script>
Setting up the options object can be cumbersome. Luckily enough, Denys Kozak has created a brilliant website that lets you configure the look and feel using a simple interface and then export the config as a json file. I played around with the config and made everything a gradient. The exported json object is below:
json
{"width":300,"height":300,"data":"https://dev.to/luckynkosi/","margin":0,"qrOptions":{"typeNumber":"0","mode":"Byte","errorCorrectionLevel":"Q"},"imageOptions":{"hideBackgroundDots":true,"imageSize":0.4,"margin":0},"dotsOptions":{"type":"extra-rounded","gradient":{"type":"linear","rotation":0,"colorStops":[{"offset":0,"color":"#7a0617"},{"offset":1,"color":"#beb819"}]}},"backgroundOptions":{"gradient":{"type":"radial","rotation":0,"colorStops":[{"offset":0,"color":"#26922d"},{"offset":1,"color":"#ecc1c1"}]}},"image":"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjM1IDIzNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjbGFzcz0icmFpbmJvdy1sb2dvIgogICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pbllNaW4gbWVldCI+CiAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICA8ZyBpZD0iODBLIj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjODhBRURDIiBwb2ludHM9IjIzNC4wNCAxNzUuNjcgMTU4LjM1IDIzMy45NSAyMDUuNTMgMjMzLjk1IDIzNC4wNCAyMTIiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBwb2ludHM9IjIzNC4wNCAxNDAuMDYgMTEyLjExIDIzMy45NSAxMTIuMTMgMjMzLjk1IDIzNC4wNCAxNDAuMDgiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBwb2ludHM9IjEzMy4yNSAwLjk1IDAuMDQgMTAzLjUxIDAuMDQgMTAzLjUzIDEzMy4yNyAwLjk1Ij48L3BvbHlnb24+CiAgICAgIDxwb2x5Z29uIGlkPSJTaGFwZSIgZmlsbD0iI0Y1OEY4RSIgZmlsbC1ydWxlPSJub256ZXJvIiBwb2ludHM9IjAuMDQgMC45NSAwLjA0IDMxLjExIDM5LjIxIDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRkVFMThBIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMzkuMjEgMC45NSAwLjA0IDMxLjExIDAuMDQgNjcuMDEgODUuODQgMC45NSI+PC9wb2x5Z29uPgogICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIGZpbGw9IiNGM0YwOTUiIGZpbGwtcnVsZT0ibm9uemVybyIgcG9pbnRzPSI4NS44NCAwLjk1IDAuMDQgNjcuMDEgMC4wNCAxMDMuNTEgMTMzLjI1IDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjNTVDMUFFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMTMzLjI3IDAuOTUgMC4wNCAxMDMuNTMgMC4wNCAxMzkuMTIgMTc5LjQ5IDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRjdCM0NFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDAuOTUgMjI2LjY3IDAuOTUgMC4wNCAxNzUuNDUgMC4wNCAyMTEuMzggMjM0LjA0IDMxLjIiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjODhBRURDIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMTc5LjQ5IDAuOTUgMC4wNCAxMzkuMTIgMC4wNCAxNzUuNDUgMjI2LjY3IDAuOTUiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRjU4RjhFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDMxLjIgMC4wNCAyMTEuMzggMC4wNCAyMzMuOTUgMTguMDcgMjMzLjk1IDIzNC4wNCA2Ny42NSI+PC9wb2x5Z29uPgogICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIGZpbGw9IiNGRUUxOEEiIGZpbGwtcnVsZT0ibm9uemVybyIgcG9pbnRzPSIyMzQuMDQgNjcuNjUgMTguMDcgMjMzLjk1IDY0LjcgMjMzLjk1IDIzNC4wNCAxMDMuNTYiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjRjNGMDk1IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDEwMy41NiA2NC43IDIzMy45NSAxMTIuMTEgMjMzLjk1IDIzNC4wNCAxNDAuMDYiPjwvcG9seWdvbj4KICAgICAgPHBvbHlnb24gaWQ9IlNoYXBlIiBmaWxsPSIjNTVDMUFFIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHBvaW50cz0iMjM0LjA0IDE0MC4wOCAxMTIuMTMgMjMzLjk1IDE1OC4zNSAyMzMuOTUgMjM0LjA0IDE3NS42NyI+PC9wb2x5Z29uPgogICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIGZpbGw9IiNGN0IzQ0UiIGZpbGwtcnVsZT0ibm9uemVybyIgcG9pbnRzPSIyMzQuMDQgMjEyIDIwNS41MyAyMzMuOTUgMjM0LjA0IDIzMy45NSI+PC9wb2x5Z29uPgogICAgICA8ZyBpZD0iR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM3LjAwMDAwMCwgNzcuMDAwMDAwKSIgZmlsbD0iI0ZGRkZGRiI+CiAgICAgICAgPHBhdGggZD0iTTI4LjIzNzE1MTcsMC43NSBDMzIuNzUxMDgzNiwxLjcgMzYuMDExMTQ1NSwzLjU1IDM5LjM3MTUxNyw3LjA1IEM0Mi40MzA5NTk4LDEwLjI1IDQ0LjMzNjg0MjEsMTMuOSA0NS4xMzkzMTg5LDE4IEM0NS43OTEzMzEzLDIxLjQ1IDQ1Ljc5MTMzMTMsNTguNTUgNDUuMTM5MzE4OSw2Mi4wNSBDNDMuNDM0MDU1Nyw3MS4xNSAzNS42NjAwNjE5LDc4LjI1IDI2LjAzMDM0MDYsNzkuNSBDMjQuMDI0MTQ4Niw3OS43NSAxNy4zMDM0MDU2LDgwIDExLjE4NDUyMDEsODAgTC03LjEwNTQyNzM2ZS0xNSw4MCBMLTcuMTA1NDI3MzZlLTE1LDEuNDIxMDg1NDdlLTE0IEwxMi40MzgzOTAxLDEuNDIxMDg1NDdlLTE0IEMyMS4yNjU2MzQ3LDEuNDIxMDg1NDdlLTE0IDI1Ljc3OTU2NjYsMC4yIDI4LjIzNzE1MTcsMC43NSBaIE0xNC41NDQ4OTE2LDQwIEwxNC41NDQ4OTE2LDY1LjYgTDE5LjcxMDgzNTksNjUuNCBDMjQuMTc0NjEzLDY1LjI1IDI1LjEyNzU1NDIsNjUuMDUgMjcuMTMzNzQ2MSw2My45IEMzMS4wNDU4MjA0LDYxLjYgMzEuMDk1OTc1Miw2MS40NSAzMS4wOTU5NzUyLDM5LjcgQzMxLjA5NTk3NTIsMTguNSAzMS4wOTU5NzUyLDE4LjUgMjcuNDM0Njc0OSwxNi4xIEMyNS42MjkxMDIyLDE0LjkgMjQuODc2NzgwMiwxNC43NSAxOS45NjE2MDk5LDE0LjU1IEwxNC41NDQ4OTE2LDE0LjQgTDE0LjU0NDg5MTYsNDAgWiIKICAgICAgICAgICAgICBpZD0iQ29tYmluZWQtU2hhcGUiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOTMuNzg5NDczNyw3LjI1IEw5My43ODk0NzM3LDE0LjUgTDY4LjIxMDUyNjMsMTQuNSBMNjguMjEwNTI2MywzMi41IEw4My43NTg1MTM5LDMyLjUgTDgzLjc1ODUxMzksNDcgTDY4LjIxMDUyNjMsNDcgTDY4LjMxMDgzNTksNTYuMSBMNjguNDYxMzAwMyw2NS4yNSBMODEuMTUwNDY0NCw2NS40IEw5My43ODk0NzM3LDY1LjUgTDkzLjc4OTQ3MzcsODAgTDc4Ljk5MzgwOCw4MCBDNjIuNTQzMDM0MSw4MCA1OS45ODUxMzkzLDc5LjcgNTcuMzc3MDg5OCw3Ny40IEM1My43MTU3ODk1LDc0LjIgNTMuOTE2NDA4Nyw3Ni4yNSA1My43NjU5NDQzLDQxLjEgQzUzLjY2NTYzNDcsMTkuMiA1My44MTYwOTkxLDguODUgNTQuMTY3MTgyNyw3LjQ1IEM1NC44NjkzNDk4LDQuODUgNTcuODI4NDgzLDEuNjUgNjAuNDM2NTMyNSwwLjc1IEM2MS45OTEzMzEzLDAuMiA2NS45MDM0MDU2LDAuMDUgNzguMTQxMTc2NSw0LjI2MzI1NjQxZS0xNCBMOTMuNzg5NDczNyw0LjI2MzI1NjQxZS0xNCBMOTMuNzg5NDczNyw3LjI1IFoiCiAgICAgICAgICAgICAgaWQ9IlBhdGgiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNMTI1LjQzNzE1MiwyOC4xIEMxMjkuMTQ4NjA3LDQyLjM1IDEzMi4yNTgyMDQsNTMuNyAxMzIuMzU4NTE0LDUzLjM1IEMxMzIuNTA4OTc4LDUzIDEzNS42Njg3MzEsNDAuOTUgMTM5LjQzMDM0MSwyNi41IEwxNDYuMzAxNTQ4LDAuMjUgTDE1NC4xMjU2OTcsMC4xIEMxNjAuMDQzOTYzLDcuMTA1NDI3MzZlLTE1IDE2MiwwLjE1IDE2MiwwLjYgQzE2MiwxLjA1IDE0NC42NDY0NCw2Ni44IDE0My42NDMzNDQsNzAuMSBDMTQyLjk0MTE3Niw3Mi40IDEzOS4xNzk1NjcsNzcuMSAxMzcuMDczMDY1LDc4LjM1IEMxMzQuNDE0ODYxLDc5Ljg1IDEzMC41MDI3ODYsODAuMSAxMjguMDk1MzU2LDc4Ljg1IEMxMjUuOTM4Nyw3Ny43NSAxMjMuMDc5ODc2LDc0LjQ1IDEyMS42MjUzODcsNzEuMzUgQzEyMC43MjI2MDEsNjkuNDUgMTA1Ljk3NzA5LDE1LjM1IDEwMi41NjY1NjMsMS4zNSBMMTAyLjIxNTQ4LDAgTDExMC4wMzk2MjgsMCBDMTE3LjcxMzMxMywwIDExNy45MTM5MzIsMCAxMTguMzE1MTcsMS4xIEMxMTguNTE1Nzg5LDEuNzUgMTIxLjcyNTY5NywxMy45IDEyNS40MzcxNTIsMjguMSBaIgogICAgICAgICAgICAgIGlkPSJQYXRoIj48L3BhdGg+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=","dotsOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#6a1a4c","color2":"#6a1a4c","rotation":"0"}},"cornersSquareOptions":{"type":"extra-rounded","gradient":{"type":"radial","rotation":0.017453292519943295,"colorStops":[{"offset":0,"color":"#3e747e"},{"offset":1,"color":"#de720d"}]}},"cornersSquareOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#000000","color2":"#000000","rotation":"0"}},"cornersDotOptions":{"type":"","gradient":{"type":"radial","rotation":0,"colorStops":[{"offset":0,"color":"#ff0000"},{"offset":1,"color":"#1c93ce"}]}},"cornersDotOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#000000","color2":"#000000","rotation":"0"}},"backgroundOptionsHelper":{"colorType":{"single":true,"gradient":false},"gradient":{"linear":true,"radial":false,"color1":"#ffffff","color2":"#ffffff","rotation":"0"}}}
Solution - Part 2: Decorate the space
The qr-code-styling example above appends the generated QR code to an element with a "canvas" id. For our token, we can add text around the code and style it accordingly. I'll leave the styling to you but the HTML for the text components can be added as shown below:
html
<body>
<div id="output">
<p>Congratulations. To redeem your token, scan the below Code</p>
<div id="canvas"></div>
<p>OR</p>
<p>
Enter the below claim code on the redemption site to redeem your token
</p>
<p><strong class="guid"></strong></p>
</div>
</div>
</body>
The below is an example of what the above code renders.
Solution - Part 3: Downloading the token.
At this point, we have everything we need on the screen in HTML. The next step download everything we see, including the borders, as an image. To achieve this, we need to "screenshot" the part of the web page we want to download as image and download the result. We can use a HTML2Canvas to achieve this.
As described in the docs, "The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page."
Similar to qr-code-styling, we can import the script using the following:
javascript
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-rc.5/dist/html2canvas.min.js"></script>
and then implement the "screenshot" and download using the following code:
javascript
html2canvas(document.getElementById('output'))
.then((canvas) => {
let link = document.createElement("a");
link.download = 'fileName.png';
link.href = canvas.toDataURL("image/jpg");;
link.click();
});
The html2canvas library takes in the target html element and returns a promise with a new canvas element. We can then create a new download link with the new canvas (converted to data URL) as the content of the link, specify the filename and then "click" it to download the resultant canvas.
Load the page and watch the screen populate and the "output" div download as a .png file.
I hope you found this article useful and I'd love to see what you build with these tools. Please feel free to share in the comments.
Top comments (7)
I'd advise against using the more rounded options on QR codes, as they tend to be the most difficult to read. This can cause problems for older devices, or for people who might have a disability which prevents them from holding the phone as still as required to read the code. Best results are the less-pretty looking square pixel QR codes.
That's a great point. Thank you.
Nice tut, am gonna try to build something with it.
Great tutorial!
Here is the link to denys kozak qr code config page
qr-code-styling.com/
Nice tutorial! Quick FYI, the script src for HTML2Canvas is still for the QR code package.
Thank you for the headups, I've fixed the script tag.
i need help about this . Is anyone here ?