DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Creating a QR Code with custom icon and text using HTML & JavaScript
Nhlanhla Lucky Nkosi
Nhlanhla Lucky Nkosi

Posted on • Updated on

Creating a QR Code with custom icon and text using HTML & JavaScript

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:

<script type="text/javascript" src="https://unpkg.com/qr-code-styling@1.5.0/lib/qr-code-styling.js"></script>
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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:

{"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"}}}
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

The below is an example of what the above code renders.
screenshot of what the QR code looks like

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:

<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-rc.5/dist/html2canvas.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

and then implement the "screenshot" and download using the following code:

    html2canvas(document.getElementById('output'))
    .then((canvas) => {
        let link = document.createElement("a");
        link.download = 'fileName.png';
        link.href = canvas.toDataURL("image/jpg");;
        link.click();
    });
Enter fullscreen mode Exit fullscreen mode

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 (6)

Collapse
 
ashleyjsheridan profile image
Ashley Sheridan

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.

Collapse
 
luckynkosi profile image
Nhlanhla Lucky Nkosi

That's a great point. Thank you.

Collapse
 
drex7 profile image
Derrick Asamoah

Nice tut, am gonna try to build something with it.

Collapse
 
passivetools profile image
PassiveTools

Great tutorial!

Here is the link to denys kozak qr code config page
qr-code-styling.com/

Collapse
 
r3volution11 profile image
Doug C. Hardester

Nice tutorial! Quick FYI, the script src for HTML2Canvas is still for the QR code package.

Collapse
 
luckynkosi profile image
Nhlanhla Lucky Nkosi • Edited on

Thank you for the headups, I've fixed the script tag.

In defense of the modern web

I expect I'll annoy everyone with this post: the anti-JavaScript crusaders, justly aghast at how much of the stuff we slather onto modern websites; the people arguing the web is a broken platform for interactive applications anyway and we should start over;

React users; the old guard with their artisanal JS and hand authored HTML; and Tom MacWright, someone I've admired from afar since I first became aware of his work on Mapbox many years ago. But I guess that's the price of having opinions.