Crear y leer QRs con JavaScript QrCode y JsQR

Programa funciones para crear y leer QRs con JavaScript QrCode y JsQR. Directamente en el navegador, y de forma totalmente dinámica.

Al final de este artículo habrás programado una web capaz de generar y escanear códigos QR con JavaScript, gracias a las librerías QrCode y JsQR. Puedes ver el ejercicio terminado haciendo clic en la siguiente imágen.

Ejercicio terminado para generar y leer códigos QR
Ejercicio terminado para generar y leer códigos QR. Haz click en la imágen para abrilo en una ventana nueva

Si no quieres leer la introducción, y prefieres ir directamente a la parte práctica, haz click aquí

Estoy convencido de que tú también notaste el resurgimiento del uso de QRs, debido a la pandemia de finales de 2019.

Especialmente en restaurantes, bares y cafeterías, donde había una mayor interacción social, y por lo tanto, donde era más necesario reducir el contacto físico.

Por ese motivo, los códigos QR reaparecieron, entre otros, como sustitutos de las cartas físicas, ofreciéndo a los comensales una versión digital.

De hecho, varias ideas de negocio se construyeron alrededor de esta premisa.

De la noche a la mañana, aparecieron plataformas online para digitalizar cartas, y acceder a ellas, escaneando códigos QR.

Por ese motivo, el artículo de hoy va dedicado a ver cómo crear y leer códigos QRs con JavaScript, y las librerías jsQR y QrCode.

El laberinto de los QR
El laberinto de los QR

Porque ¿quién sabe?, puede que a tí también se te ocurra una gran idea de negocio, basada en el uso de estas mismas tecnologías.

¿Cómo funciona un código QR? 

El sistema QR (del inglés Quick Response Code) nació en 1994, como una alternativa mejorada del código de barras, gracias a los ingenieros de la empresa Denso Wave

Técnicamente, un QR es la codificación de una cadena alfanumérica en un formato de imagen

Como ya sabes, se trata de una matriz de cuadrados negros, sobre un fondo blanco.

La disposición de cada conjunto de «píxeles», describe un tipo de información u otra.

Por poner un ejemplo, los tres cuadros más llamativos indican al receptor, la posición y orientación de la imagen.

A su vez, el receptor aplica un algoritmo «Solomon error correction» para «perfilar» el input obtenido e interpretar correctamente su contenido.

En este ejemplo se indica qué representan el resto de grupos de cuadrados negros. Esquema de un código QR

Cada porción tiene su significado, de tal forma que el resultado es una pieza de información totalmente optimizada. Sin duda una obra de ingeniería fascinante.

Si quieres conocer más sobre la historia de la creación de este sistema, al final del artículo te dejo un enlace a un interesante escrito en la web de Denso Wave. 

La librería JavaScript para generar códigos QR dinámicamente.

El objetivo de ésta publicación, es implementar una solución web para trabajar con imágenes qr. 

Por un lado, debe permitir al usuario generar códigos de forma dinámica, y por el otro, leer su contenido a través de la cámara integrada en el dispositivo.

Es por eso que hoy analizaré dos librerías JavaScript. Cada una para cubrir una función distinta.

En primer lugar voy a hablar de QRCode, una librería JavaScript creada por Ryan Day, y conjuntamente mantenida por un equipo de 44 desarrolladores desde hace más de 7 años .

Su repositorio de Github ostenta nada más y nada menos que 6.1K estrellas en el momento de redactar estas líneas. Y el paquete NPM se descarga semanalmente más de 1M de media.

Sin duda, estos datos ya sugieren que se trata de un recurso popular y de una alta calidad. Pero por si necesitas más pruebas, solo tienes que echar un vistazo a las funcionalidades que ofrece:

  • Funciona tanto en servidor como en cliente (NodeJs y Navegador).
  • Ofrece funciones directamente para CLI.
  • Permite guardar el código QR como una imagen.
  • Soporta datos numéricos, alfanuméricos, kanji y modo Byte.
  • Soporta modos mixtos.
  • Soporta caracteres chinos, cirílicos, griegos y japoneses.
  • Admite emoticonos y otros caracteres especiales.
  • Genera de forma automática segmentos optimizados para una mejor comprensión y unos códigos QR más pequeños. 

Me parece especialmente interesante, el hecho de que se pueda utilizar tanto en la parte cliente como en la servidora. 

Piénsalo, la capacidad de generar códigos dinámicamente en el servidor, y entregarlos en formatos descargables como SVG, abre todo un mundo de posibilidades.

En esta sencilla guía pondré en práctica las opciones que ofrece para navegador.

El comando para instalar la dependencia es el siguiente:

npm install --save qrcode

Tras hacerlo, se importa el módulo “QRCode”, para trabajar con su API, tal como veremos a continuación.

import qrcode from "qrcode";

El objeto QRCode pone a disposición del desarrollador, una serie de métodos para generar códigos en múltiples formatos.

Una de las formas más habituales para pintar una imágen dinámicamente en web, es mediante el uso de un elemento “canvas” propio de HTML5.

Por ese motivo QRCode dispone del método “QRCode.toCanvas(texto, elementoCanvas, [opciones])”. 

Si a este le pasamos como argumentos, el texto a codificar, y un elemento canvas del DOM, dibujará el QR listo para descargar como imágen.

Un ejemplo de cómo utilizar “toCanvas” sería este:

import qrcode from "qrcode";
const canvasElement = document.querySelector('canvas.qr');
qrcode.toCanvas(canvasElement, 'https://libreriasjs.com');

Como dije, ésta es solo una forma de representación, otras funciones disponibles son:

  • create(text, [options]): Para instanciar un objeto que describe el código
  • toDataURL(canvasElement, text, [options], [cb(error)]): Para obtener una URI codificada en base 64. Ideal si se quiere abrir una pestaña de navegador que “imprima” la imágen en pantalla
  • toString(text, [options], [cb(error, url)]): Devuelve una cadena de texto con el QR formateado, por ejemplo, como un gráfico escalable SVG.
  • toCanvas(text, [options], [cb(error, string)]): Tal como veíamos en el ejemplo anterior, muestra sobre una etiqueta canvas el resultado generado.

Para programas desarrollados con NodeJs, la herramienta expone dos métodos adicionales:

  • toFile(path, text, [options], [cb(error)]): Genera un archivo en formato “png”, “svg” o “txt” en el servidor.
  • toFileStream(stream, text, [options]): Escribe la imagen en forma de “stream”.

Todas las funciones listadas aquí, admiten un objeto con notación JSON, para configurar ciertas opciones.

Puedes consultar cada una de ellas en el enlace que te dejo a continuación:

https://github.com/soldair/node-qrcode#api

Más adelante la pondremos en práctica con un sencillo ejercicio.

Escanear códigos QR, directamente en el navegador con JavaScript y jsQR.

De poco sirve crear estos códigos, si luego el usuario no dispone de una herramienta para escanearlos

Aunque a decir verdad, hoy en día, la mayoría de dispositivos móviles ya tienen esa función integrada de serie.

Sin embargo, no me negarás que implementarlo en un sitio web o aplicación, puede ser de gran utilidad si se quieren cubrir necesidades específicas.

Por eso, seguidamente voy a analizar la librería jsQR, un paquete capaz de detectar, ubicar y descifrar un código QR, a partir de una imágen dada.

Este recurso fué creado por Cosmo Wolfe, hace más de 7 años. Aun así, está más vivo que nunca, ya que se descarga más de 200K veces a la semana.

Se encuentra activamente mantenido por un equipo de 11 colaboradores, y tiene más de 3.1K estrellas en su repositorio público.

La instrucción para instalarla es:

npm install jsqr --save

A partir de entonces, podrás importarla en tu programa con el siguiente código:

import jsQR from "jsqr";

Su API es extremadamente sencilla. La variable «jsQR» es una función que admite cuatro argumentos.

En orden de entrada estos son:

  • imageData: La imagen que se escanea, en forma de variable Uint8ClampedArray de una array de píxeles RGBA. Es el mismo formato que entrega un contexto 2d de una etiqueta canvas HTML5, mediante el método “getImageData()”. En seguida lo pondremos en práctica.
  • width: Ancho de la imagen a escanear
  • height: Alto de la imagen a escanear
  • options: Un objeto opcional, para configurar parámetros opcionales.

Un ejemplo de cómo ejecutar la función, sería este

import jsQR from "jsqr";
const canvasElement = document.querySelector('canvas.cam-frame');
const ctxCanvas = canvasElement.getContext("2d", {
  willReadFrequently: true,
});
const frameData = ctxCanvas.getImageData(0,0,canvasElement.width, canvasElement.height);
const code = jsQR(
  frameData.data,
  frameData.width,
  frameData.height,
  {
    inversionAttempts: 'dontInvert'
  }
);
return code

La variable “code” retornada por la librería es un objeto que contiene toda esta información:

  • binaryData: Los bytes en “bruto” del código QR
  • data: La cadena alfanumérica codificada en el QR. Ésta es la información con la que trabajaremos, por ejemplo, redirigiendo al usuario a un sitio web.
  • chunks: Las porciones del QR
  • version: Información acerca de la versión del QR
  • location: Describe la ubicación del código en la imágen. Ideal para enmarcar la posición del QR con un cuadrado.

No dudes en acudir a la documentación oficial, si necesitas conocer más su API.

https://github.com/cozmo/jsQR#jsqr

Desarrollar una aplicación web para crear y leer QRs con JavaScript

Tras este breve vistazo a las respectivas características, pasamos a implementar una aplicación web para escanear y generar códigos QR de forma dinámica.

No va a ser un ejercicio especialmente difícil de seguir, no obstante, te dejo el enlace al repositorio, por si algún punto no está del todo claro.

https://github.com/Danivalldo/libreriasjs/tree/master/JsQR

Preparamos una estructura HTML con dos áreas bien diferenciadas.

La primera, incorpora los contenedores necesarios para crear códigos QR a partir de un “input” de texto y un elemento “canvas”.

<h1 class="is-size-4 has-background-primary has-text-light p-4 has-text-centered has-text-weight-bold">
  Generador y lector de QR
</h1>
<div class="p-3 container is-max-desktop">
  <h2 class="is-size-5 has-text-weight-bold has-text-centered">
    Crear código QR
  </h2>
  <canvas id="qr-canvas" class="is-block mx-auto"></canvas>
  <div class="field">
    <div class="control">
      <input
        class="input"
        type="text"
        placeholder="Text input"
        value="https://libreriasjs.com"
      />
    </div>
    <p class="help">
      Escribe cualquier texto o URL, y verás como se va formando el QR
    </p>
  </div>
</div>

La segunda parte mostrará un botón, para que el usuario pueda activar la cámara de su dispositivo. 

El botón irá acompañado de otra etiqueta “canvas” y un contenedor. En ellos se mostrarán los fotogramas capturados por la cámara, y el valor del QR si se detecta.

<div class="p-3 pb-5 container is-max-desktop">
  <h2 class="is-size-5 has-text-weight-bold has-text-centered mb-5">
    Leer código QR
  </h2>
  <div>
    <div
      id="qr-data"
      class="box has-text-centered is-size-5 has-text-white"
    ></div>
    <canvas id="cam-canvas" class="d-none"></canvas>
    <div class="has-text-centered p-1 mb-4">
      <button class="button is-primary" id="btn-cam">Iniciar cámara</button>
    </div>
    <p class="help">
      Haz click en el botón para iniciar la cámara, seguidamente escanea un
      código QR y comprueba como al detectarlo aparece su contenido en la
      caja superior del vídeo.
    </p>
  </div>
</div>

Para cerrar la maqueta he incluido un pié de página informativo.

<footer class="footer has-background-primary has-text-light">
  <div class="content has-text-centered">
    <p>
      <span class="has-text-weight-bold">LibreriasJs</span> -
      <a
        href="https://libreriasjs.com"
        rel="nofollow"
        class="has-text-light is-underlined"
        >libreriasjs.com</a
      >
      - 2022 - <span class="is-size-7">v.1.0.1</span>
    </p>
  </div>
</footer>

Como ya habrás identificado, los componentes tienen clases CSS. En este caso, he utilizado la librería Bulma para agregar estilos preconfigurados.

Obtener este recurso CSS es tan sencillo como ejecutar el siguiente comando:

npm install bulma

E importarlo en la hoja de estilos SASS

@import 'bulma/bulma.sass'
.video-cam
  position: fixed
  top: 0
  left: 0
  pointer-events: none
  visibility: hidden
  opacity: 0
#cam-canvas
  width: 100%
  height: auto
  display: block
  margin: 0 auto 20px auto
  &.d-none
    display: none

También he aprovechado para añadir algunas clases propias.

Para finalizar la práctica, vamos a programar el comportamiento con JavaScript. Por supuesto, debemos empezar instalando las librerías de las que hemos estado hablando

npm install jsqr qrcode 

Encapsulamos la lógica de cada herramienta en servicios independientes, en forma de clases.

Comenzamos con el generador de códigos QR. Crea una clase dentro del directorio services y llámala “QRGenerator”, asegúrate de importar la librería “qrcode” al inicio del archivo.

Prepara un constructor que reciba un elemento “canvas” como argumento, y guárdalo como propiedad en el constructor.

import qrcode from "qrcode";
class QRGenerator {
  constructor(canvasElement) {
    this.canvas = canvasElement;
  }
}
export default QRGenerator;

Solo definiremos un único método que reciba un “string” y pinte el código QR en el “canvas” guardado.

async buildQR(text) {
  try {
    await qrcode.toCanvas(this.canvas, text);
  } catch (err) {
    console.log(err);
  }
}

Solo eso será suficiente para este primer servicio.

Avanzamos en el tutorial, construyendo una clase para el lector de QRs

La complejidad aquí radica principalmente en manejar la cámara del dispositivo, más que en escanear el QR. Pero no te preocupes, vamos a ir paso a paso.

Crea un archivo con el nombre “QRReader.js” dentro del directorio “services”, e importa el recurso correspondiente al inicio.

Define una clase con el mismo nombre del archivo. Su constructor recibirá dos variables, el elemento canvas donde mostrar qué se visualiza a través de la cámara, y el contenedor en el que “imprimir” la información del QR encontrado.

import jsQR from "jsqr";
class QRReader {
  constructor(canvasVideoElement, qrDataContainerElement) {
    this.isCamReady = false;
    this.isCamOpen = false;
    this.stream = null;
    this.rafID = null;
    this.camCanvas = canvasVideoElement;
    this.qrDataContainer = qrDataContainerElement;
    this.camCanvasCtx = this.camCanvas.getContext("2d", {
      willReadFrequently: true,
    });
    this.video = document.createElement("video");
    this.video.classList.add("video-cam");
    this.video.setAttribute("playsinline", true);
    document.body.appendChild(this.video);
  }
}
export default QRReader;

Las propiedades de la clase van a ser estas:

  • isCamReady: Una booleana para determinar si la cámara se ha iniciado por primera vez.
  • isCamOpen: Otra booleana para controlar si la cámara está encendida o apagada.
  • stream: El flujo de datos extraídos directamente de la cámara.
  • rafID: El identificador de iteración de “requestAnimationFrame”. Usaremos esta API HTML5 para ejecutar el código de escaneo de forma recurrente.
  • camCanvas: Guardamos una referencia al canvas pasado como parámetro.
  • qrDataContainer: También almacenamos la referencia al contenedor donde mostrar el resultado del código encontrado.
  • camCanvasCtx: Obtenenos el contexto “2d” del canvas. Será necesario, para pintar cada fotograma de la cámara en el área definida por el canvas.
  • video: Creamos un elemento video. En una primera instancia, el “stream” obtenido de la cámara se plasmará en un vídeo, para posteriormente re-pintarlo en el canvas. Para evitar que el usuario vea esta etiqueta intermedia, la agregamos con unos estilos adicionales, y el atributo “playsinline”.

Escalamos la clase QRReader programando más métodos en ella.

getIsCamOpen() {
  return this.isCamOpen;
}

Exponemos el estado de la variable “isCamOpen” con el método “getIsCamOpen”.

Un objeto instanciado de esta clase debe controlar la activación de la cámara, por ese motivo preparamos un método “toogleCamera”.

async toggleCamera() {
  if (this.isCamOpen) {
    if (this.rafID) {
      cancelAnimationFrame(this.rafID);
    }
    this.video.pause();
    this.stream.getTracks().forEach((track) => {
      track.stop();
    });
    this.isCamOpen = false;
    this.camCanvasCtx.clearRect(
      0,
      0,
      this.camCanvas.width,
      this.camCanvas.height
    );
    this.camCanvas.classList.add("d-none");
    this.qrDataContainer.innerHTML = "";
    this.qrDataContainer.classList.remove("has-background-success");
    return;
  }
  this.isCamOpen = true;
  this.camCanvas.classList.remove("d-none");
  this.stream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: "environment" },
  });
  console.log(this.stream);
  this.video.srcObject = this.stream;
  this.video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
  this.video.play();
  requestAnimationFrame(this.tick.bind(this));
}

Si la cámara está abierta, deberemos detener el “loop” iniciado por “requestAnimationFrame”. Pausar el vídeo, finalizar el flujo de datos y actualizar la variable “isCamOpen”.

Además, limpiaremos el canvas , y actualizaremos la interfaz de usuario.

En caso de que la cámara esté parada, cambiaremos la boleana a “true”, actualizamos la UI, y solicitamos acceso al dispositivo mediante “getusermedia”.

Si todo es correcto, alimentamos la etiqueta vídeo con el flujo de datos “stream”. Para seguidamente, hacer “play” e iniciar la llamada a “requestAnimationFrame”.

Cada iteración de “requestAnimationFrame” ejecutará la función “tick”. 

tick() {
  if (!this.isCamOpen) {
    return;
  }
  if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
    if (!this.isCamReady) {
      const camSize = this.video.getBoundingClientRect();
      if (camSize.width && camSize.height) {
        this.camCanvas.width = camSize.width;
        this.camCanvas.height = camSize.height;
        this.isCamReady = true;
      }
    }
    this.camCanvasCtx.drawImage(
      this.video,
      0,
      0,
      this.camCanvas.width,
      this.camCanvas.height
    );
    const imageData = this.camCanvasCtx.getImageData(
      0,
      0,
      this.camCanvas.width,
      this.camCanvas.height
    );
    const code = jsQR(imageData.data, imageData.width, imageData.height, {
      inversionAttempts: "dontInvert",
    });
    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.qrDataContainer.innerHTML = code.data;
      this.qrDataContainer.classList.add("has-background-success");
    }
  }
  this.rafID = requestAnimationFrame(this.tick.bind(this));
}

El método “tick” empieza comprobando si la cámara está activa. De ser así, comprobará es la primera vez que se ejecuta, gracias a la propiedad “readyState” y “isCamReady”.

En la primera iteración obtenemos el tamaño del vídeo, y seteamos el “canvas” para que tengan las mismas dimensiones.

Acto seguido, se pinta la imagen del fotograma actual en el canvas con “drawImage()”, y se extrae información de dicho fotograma con “getImageData()”.

Esa información será la que deberemos enviar a la librería “jsQR” para determinar si hay un código en ella.

En caso afirmativo, dibujamos un rectángulo para enmarcar en rojo dónde se encuentra el código en cuestión, y exponemos el contenido descifrado, en el contenedor “qrDataContainer”.

Para generar cada línea del marco, nos apoyamos en el método «drawLine» y de la información contenida en la variable “code”.

drawLine(begin, end, color) {
  this.camCanvasCtx.beginPath();
  this.camCanvasCtx.moveTo(begin.x, begin.y);
  this.camCanvasCtx.lineTo(end.x, end.y);
  this.camCanvasCtx.lineWidth = 4;
  this.camCanvasCtx.strokeStyle = color;
  this.camCanvasCtx.stroke();
}

Finalizamos el método “tick” llamándolo de nuevo con “requestAnimationFrame”.

Ahora que ya disponemos de los dos servicios para controlar cada función, vamos a programar el script principal. Básicamente, se encargará de instanciar las clases, y conectar sus métodos al DOM.

Incluye un nuevo documento en la raíz de tu proyecto, y llámalo “main.js”. En las primeras líneas importa los estilos, así como los servicios que hemos creado anteriormente.

import "./style.sass";
import QRGenerator from "./services/QRGenerator";
import QRReader from "./services/QRReader";

Declara variables que apunten a distintos elementos del “Document Object Model”. Tal como aparece en el bloque que sigue.

const input = document.querySelector("input");
const cameraBtn = document.querySelector("#btn-cam");
const qrCanvas = document.querySelector("#qr-canvas");
const camCanvas = document.querySelector("#cam-canvas");
const qrDataContainer = document.querySelector("#qr-data");

Instancia un objeto de cada servicio, pasándo los argumentos necesarios.

const qrGenerator = new QRGenerator(qrCanvas);
const qrReader = new QRReader(camCanvas, qrDataContainer);

Escucha cuando el usuario escribe un nuevo valor en la caja de texto, y llama al método “buildQR” para actualizar la imágen del código generado dinámicamente.

input.addEventListener("input", (e) => {
  const text = e.target.value;
  qrGenerator.buildQR(text);
});

Deberás ejecutar al menos una vez ese mismo método, para que construya un QR con el valor por defecto de la caja de texto.

qrGenerator.buildQR(input.getAttribute("value"));

Por último, escucha cuando un usuario hace click en el botón de activar la cámara, y en el “callback” asignado, lanza el método “toggleCamera()”. A continuación actualiza el texto del botón según el estado de la cámara.

cameraBtn.addEventListener("click", async () => {
  qrReader.toggleCamera();
  if (qrReader.getIsCamOpen()) {
    cameraBtn.innerHTML = "Parar cámara";
    return;
  }
  cameraBtn.innerHTML = "Iniciar cámara";
});

Incluir un QR en un PDF generado dinámicamente con JavaScript

Permíteme terminar la publicación lanzando un pequeño reto

Estarás de acuerdo conmigo en que mostrar códigos QR en la pantalla de una página web, carece de sentido.

El lugar más adecuado para mostrar este tipo de códigos, es sobre un soporte físico, como por ejemplo, impreso en una entrada de teatro.

Por eso, te animo a que intentes programar un sistema capaz de crear un documento PDF de forma dinámica, con un código QR en él.

Para ayudarte en ésta tarea, te dejo un enlace al estudio de una librería para crear esta clase de archivos con JavaScript. Tu objetivo será el de combinar ese recurso con lo aprendido hoy.

Crear PDFs con JavaScript

Espero que este post te haya resultado de ayuda en tu proyecto. Te listo una serie de enlaces a material adicional.

¡Hasta la próxima, desarrollador! 

6 comentarios en «Crear y leer QRs con JavaScript QrCode y JsQR»

Deja un comentario