はじめに
こんにちは、しおです。少し前までひどく暑かったのに、もうすっかり年の瀬ですね。
冬っぽい画像を生成してみたので、ぜひ癒されてください。
癒されましたね。
さて、いま私が開発で携わっているプロダクトにて、『QR コード*1をアプリケーション内で生成して画像として表示&ダウンロードしたい。その際に、任意のタイトルを画像に埋め込みたい。』という要件が発生しました。
そこで、今回はその対応で実際に使用した qrcode.react
という React 向けのライブラリを用いて React アプリケーション内で QR コードとタイトルを描画したうえで画像ファイルとしてダウンロードさせるまでの流れを紹介します。
目次
qrcode.react
の紹介
QR コード生成用のライブラリとして、qrcode.react
を使用します。
このライブラリは、QR コードを Canvas または SVG で描画でき、JSX でシンプルな記述かつ柔軟なカスタマイズができるためお気に入りです。
公式ドキュメントでも推奨されているように SVG の方が設定に柔軟性があるようですが、今回は後からタイトルを追加で描画するために Canvas を使用します。
qrcode.react
では、コンポーネントの読み込み時にいくつかのプロパティを設定するだけで QR コードのアプリケーション内での生成が可能です。
いろいろなプロパティ
先述した通り QR コード描画時にいろいろなプロパティが設定できますが、代表的なものをいくつかピックアップして紹介します。
title
QR コードにタイトルを付与できます。ただし、これは今回のように SVG や Canvas 内に描画するためのものではなく、アクセシビリティやSEO目的で SVG や Canvas に title
要素を埋め込むためのものです。
私がやりたかったことは QR コードの下にタイトルを表示することだったので、このプロパティを使用するのではなく自前で実装することにしました。
level
QR コードのエラー訂正レベルを指定するプロパティです。
なお、エラー訂正レベルは、QR コードが部分的に破損した場合でもデータを復元できる割合を示します。
L (Low)
: データの約7%を復元可能。最も冗長性が低く、その分容量も最小。M (Medium)
: データの約15%を復元可能(デフォルト)。Q (Quartile):
データの約25%を復元可能。H (High):
データの約30%を復元可能。最も冗長性が高く、信頼性が必要な場合に推奨される。
印刷するような用途でなければデフォルトの M
でよいと思いますが、後述する画像埋め込みで複雑な画像を埋め込む際は、高めのレベルに設定しておくと安心かもしれません。
imageSettings
QR コードの中央に画像を埋め込むための設定を行うプロパティです。任意の画像を QR コード内に配置できます。 企業ロゴやブランドロゴ等を指定するケースが多そうです。
src
: 埋め込む画像のURL。height
: 埋め込む画像の高さ。width
: 埋め込む画像の幅。excavate
: 画像部分の背景(QR コード)を削除するかどうか(true/false)。x
: 画像の左上の X座標(QR コードの左上を基準)を指定。デフォルトは QR コードの中央に自動配置。y
: 画像の左上の Y座標(QR コードの左上を基準)を指定。デフォルトは QR コードの中央に自動配置。opacity
: 画像の透明度(0: 完全に透明 から 1: 完全に不透明)を指定。デフォルト値は 1。crossOrigin
: クロスオリジンの画像を取得する際のリクエスト設定を指定。anonymous
(認証情報なし) またはuse-credentials
(認証情報あり)を指定。
作業手順
実際に qrcode.react
を React プロジェクトに導入して実装していく手順を紹介します。なお、React プロジェクト自体の構築手順については今回触れません。
今回の実装で使用したライブラリやフレームワークのバージョンは以下の通りです。同じ環境で再現したい方は、これらのバージョンを参考にしてください。
- React: 18.3.1
- qrcode.react: 4.1.0
インストール
まずは qrcode.react
ライブラリをプロジェクトにインストールします。
npm install qrcode.react
基本的な QR コードの描画機能を実装
早速、QR コードを React コンポーネントとして描画してみます。
以下のように QRCodeCanvas
コンポーネントを使用して、ユーザーが入力した URL を元に QR コードを生成します。
import React, { useState } from 'react'; import { QRCodeCanvas } from 'qrcode.react'; // 今回は Canvas を利用 const QR_CANVAS_SIZE = 200; // 初期値は 128 const App = () => { const [url, setUrl] = useState(''); return ( <div style={{ textAlign: 'center', marginTop: '50px' }}> <h1>QR Code Generator</h1> <input type="text" value={url} onChange={(e) => setUrl(e.target.value)} placeholder="Enter URL" style={{ padding: '10px', width: '300px' }} /> <div style={{ marginTop: '20px' }}> <QRCodeCanvas value={url} size={QR_CANVAS_SIZE} />; </div> </div> ); } export default App;
これで、ユーザーが入力した URL に基づいて QR コードが生成されるシンプルなアプリケーションが完成しました。簡単ですね。
ユーザーが入力したタイトルを追加で描画する機能を実装
次に、生成された QR コードの下にユーザーが入力したタイトルを描画してみます。canvas の 2D 描画コンテキスト (getContext('2d')
) を利用して、テキストを追加します。
まずは、共通で使用する定数類を記述します。
const QR_PADDING = 40; const QR_PADDING_VERTICAL = QR_PADDING * 2; const QR_PADDING_HORIZONTAL = QR_PADDING * 4; const QR_CANVAS_SIZE = 200; // 初期値は 128 const TITLE_PADDING = 5; const TITLE_PADDING_VERTICAL = TITLE_PADDING * 2; const TITLE_FONT_SIZE = 30; const TITLE_FONT_FAMILY = 'sans-serif'; const TITLE_BG_COLOR = '#fafafa'; const TITLE_FG_COLOR = '#131313';
次に、描画用の関数を定義します。タイトルの描画位置を決めるのに手こずっていますね。
const drawQRCodeWithTitle = ( qrCodeCanvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, qrCodeImage: HTMLImageElement, title: string ) => { const titleHeight = (TITLE_FONT_SIZE + TITLE_PADDING); const qrAreaHeight = QR_PADDING_VERTICAL + qrCodeImage.height; const titleAreaHeight = titleHeight + TITLE_PADDING_VERTICAL; qrCodeCanvas.height = QR_AREA_HEIGHT + TITLE_AREA_HEIGHT; // 背景を準備 ctx.fillStyle = TITLE_BG_COLOR; ctx.fillRect(0, 0, qrCodeCanvas.width, qrCodeCanvas.height); // QR コードを描画 ctx.drawImage(qrCodeImage, QR_PADDING_VERTICAL, QR_PADDING); // タイトルを描画 const titleXPosition = qrCodeCanvas.width / 2; const titleYPosition = qrCodeImage.height + QR_PADDING_VERTICAL; ctx.fillStyle = TITLE_FG_COLOR; ctx.font = `${TITLE_FONT_SIZE}px ${TITLE_FONT_FAMILY}`; ctx.textAlign = 'center'; ctx.fillText(title, titleXPosition, titleYPosition); };
最後に、URL もしくはタイトルが変更されたときに先述の drawQRCodeWithTitle()
関数を実行する useEffect
フックを作成します。
useEffect(() => { const canvas = document.querySelector('canvas'); // ※① if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const qrCodeImage = new Image(); qrCodeImage.src = canvas.toDataURL('image/png'); // ※② qrCodeImage.onload = () => { canvas.width = qrCodeImage.width + QR_PADDING_HORIZONTAL; canvas.style.display = 'block'; drawQRCodeWithTitle(canvas, ctx, qrCodeImage, title); }; }, [url, title]);
※① document.querySelector('canvas')
では単純に最初に見つかった canvas
要素を返すため、本来は ref で操作対象の DOM を指定するのが望ましいですが、今回は QR コードを一つだけ描画するため簡易的な記述を採用しています。
※② toDataURL()
は Canvas の内容をエンコードしてデータ URL として取得するため、Canvas のサイズが大きい場合や高解像度の画像を扱う場合、処理に時間がかかることがあり頻繁に呼び出すとパフォーマンスの低下を招く可能性があります。
これで、ユーザーが入力したタイトルを QR コードの下に表示させることができました。
ダウンロード機能を実装
最後に、ユーザーが生成した QR コードとタイトルを含む画像をダウンロードできる機能を追加します。
まずはダウンロード処理用の関数を実装。QR コードを描画した QRCodeCanvas
から HTMLImageElement
を生成し、その画像を PNG 形式でダウンロードさせます。
canvas.toDataURL()
を使って画像データを取得し、a
要素を利用してダウンロード可能にしています。
const downloadQRCodeImage = () => { const canvas = document.querySelector('canvas'); if (!canvas) return; const link = document.createElement('a'); link.download = title ? `${title}_qrcode.png` : `qrcode.png`; const qrCodeImage = new Image(); qrCodeImage.src = canvas.toDataURL('image/png'); qrCodeImage.onload = () => { link.href = canvas.toDataURL('image/png'); link.click(); }; };
ボタンも適当に作っておきます。
<button type="button" onClick={downloadQRCodeImage} style={{ padding: '10px 10px', fontSize: '12px', color: '#fff', backgroundColor: '#a4a4a4', borderRadius: '5px', cursor: 'pointer', }} > Download </button>
これで、ユーザーは QR コードと入力したタイトルが描かれた Canvas を PNG ファイルとしてダウンロードできるようになります!
全文は下記に貼っておきます。
コード全文
import React, { useState, useRef, useEffect } from 'react'; import { QRCodeCanvas } from 'qrcode.react'; // 今回は Canvas を利用 // 定数類 const QR_PADDING = 40; const QR_PADDING_VERTICAL = QR_PADDING * 2; const QR_PADDING_HORIZONTAL = QR_PADDING * 4; const QR_CANVAS_SIZE = 200; // 初期値は 128 const TITLE_PADDING = 5; const TITLE_PADDING_VERTICAL = TITLE_PADDING * 2; const TITLE_FONT_SIZE = 30; const TITLE_FONT_FAMILY = 'sans-serif'; const App = () => { const [url, setUrl] = useState(''); const [title, setTitle] = useState(''); const qrCodeRef = useRef(null); const drawQRCodeWithTitle = ( qrCodeCanvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, qrCodeImage: HTMLImageElement, title: string ) => { const titleHeight = (TITLE_FONT_SIZE + TITLE_PADDING); const qrAreaHeight = QR_PADDING_VERTICAL + qrCodeImage.height; const titleAreaHeight = titleHeight + TITLE_PADDING_VERTICAL; qrCodeCanvas.height = qrAreaHeight + titleAreaHeight; // 背景を準備 ctx.fillStyle = TITLE_BG_COLOR; ctx.fillRect(0, 0, qrCodeCanvas.width, qrCodeCanvas.height); // QR コードを描画 ctx.drawImage(qrCodeImage, QR_PADDING_VERTICAL, QR_PADDING); // タイトルを描画 const titleXPosition = qrCodeCanvas.width / 2; const titleYPosition = qrCodeImage.height + QR_PADDING_VERTICAL; ctx.fillStyle = TITLE_FG_COLOR; ctx.font = `${TITLE_FONT_SIZE}px ${TITLE_FONT_FAMILY}`; ctx.textAlign = 'center'; ctx.fillText(title, titleXPosition, titleYPosition); }; const downloadQRCodeImage = () => { const canvas = document.querySelector('canvas'); if (!canvas) return; const qrCodeImage = new Image(); qrCodeImage.src = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.download = title ? `${title}_qrcode.png` : `qrcode.png`; qrCodeImage.onload = () => { link.href = canvas.toDataURL('image/png'); link.click(); }; }; useEffect(() => { const canvas = document.querySelector('canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const qrCodeImage = new Image(); qrCodeImage.src = canvas.toDataURL('image/png'); qrCodeImage.onload = () => { canvas.width = qrCodeImage.width + QR_PADDING_HORIZONTAL; canvas.style.display = 'block'; drawQRCodeWithTitle(canvas, ctx, qrCodeImage, title); }; }, [url, title]); return ( <div style={{ textAlign: 'center', marginTop: '50px' }}> <h1>QR Code Generator with Title</h1> <input type='text' value={url} onChange={e => setUrl(e.target.value)} placeholder='Enter URL' style={{ padding: '10px', width: '200px', marginBottom: '20px' }} /> <input type='text' value={title} onChange={e => setTitle(e.target.value)} placeholder='Enter Title' style={{ padding: '10px', width: '200px', marginBottom: '20px' }} /> <div style={{ marginTop: '20px', display: 'flex', justifyContent: 'center' }} ref={qrCodeRef}> <QRCodeCanvas value={url} size={QR_CANVAS_SIZE} />; </div> <button type="button" onClick={downloadQRCodeImage} style={{ padding: '10px 10px', fontSize: '12px', color: '#fff', backgroundColor: '#a4a4a4', borderRadius: '5px', cursor: 'pointer', }} > Download </button> </div> ); } export default App;
まとめ
React で QR コードを生成し、さらにその下にユーザーが入力したタイトルを Canvas に描画する方法を紹介しました。
今回は深くカスタマイズしませんでしたが、qrcode.react
はプロパティも数多く存在するため、よりカスタマイズした機能を作成することも可能です。
今回、技術ブログでは3回目の執筆をさせていただきました。
1回目と2回目は、それぞれ下記のような記事を書いていたので、お時間ある方はこちらもぜひ読んでいってください!
techblog.styleedge.co.jp techblog.styleedge.co.jp
また、スタイル・エッジでは、一緒に働く仲間を絶賛大募集しています。
もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください!
せっかくなので、最後に qrcode.react
で作成した QR コードも掲載しておきます!それではまた!
*1:※QRコードは株式会社デンソーウェーブの登録商標です。