はじめに
この記事の概要
こんにちは、株式会社TOKOSの杉田です!
今回はReactを用いたQRコードのダウンロードボタンを作成します!
QRコード生成用のライブラリの比較もしましたので、最後まで見ていただけると幸いです!
この記事では、以下の内容について詳しく解説します:
- QRコード生成用のライブラリ
- Blobデータの変換
対象読者
- Reactを用いたフロントエンド開発を行っている方
- QRコード生成ライブラリを選定している方
この記事で扱う内容、扱わない内容
この記事で扱う内容 :
- QRコード生成ライブラリについて
- Blobデータの生成・変換方法
この記事で扱わない内容:
- JavaScriptの基本的な使用方法
- Tailwindcssについて
今回の成果物
今回はダウンロードボタンを設け、非表示状態のQRコードをダウンロードできるボタンを作成いたします!
開発環境
- next: 15.1.4
- react: ^19.0.0
- tailwindcss: ^4.0.1
QRコード生成ライブラリの比較(2025年 3月)
まずはQRコード生成ライブラリの選定を行います。
今回は2つのライブラリを比較します。
比較ライブラリは下記になります。
- react-qr-code
- qrcode.react
比較項目は下記になります。
- Weekly Download数
- 最終更新日
- パッケージサイズ(Minified)
Weekly Download数
react-qr-code | qrcode.react |
---|---|
373,477 | 1,459,090 |
最終更新日
react-qr-code | qrcode.react |
---|---|
9ヶ月前 | 3ヶ月前 |
パッケージサイズ(Minified)
react-qr-code | qrcode.react |
---|---|
16.8kB | 16.4kB |
比較結果として3つの項目全てで「qrcode.react」に軍配があがりました!
今回は「qrcode.react」を使用しようと思います!
実装
今回はPNGデータでダウンロードすることを目標とします。
今回はQRコードは隠し、ダウンロードのみできる実装としたいのでhidden(display:none)で隠しています!
またQRCodeはSVGとしています!
"use client"
import { QRCodeSVG } from "qrcode.react"
export function QrCodeDownloadButton({ profileIdPromise, clientRoleType }: Props) {
const url = "https://to-ko-s.com"
return (
<>
<button>
QRコードダウンロード
</button>
{/* display:noneで隠しています */}
<div className="hidden">
<QRCodeSVG value={url} size={512} level="H" />
</div>
</>
)
}
QRCodeSVGに渡しているPropsは下記になります。
- value={url}
- QRコードに埋め込むデータ(URL)を指定します
- size={512}
- QRコードのサイズをピクセル単位で指定します。この場合、512×512ピクセルのQRコードが生成されます。
- level=”H”
- QRコードのエラー訂正レベルを指定します。”H”は最も高いエラー訂正レベルで、QRコードの約30%が損傷しても読み取り可能になります!
他のレベルには”L”(低)、”M”(中)、”Q”(四分位)があります。
- QRコードのエラー訂正レベルを指定します。”H”は最も高いエラー訂正レベルで、QRコードの約30%が損傷しても読み取り可能になります!
またSVGとしている理由は変換の柔軟性を優先しているためになります!
まずはDOMにアクセスするためにuseRefを使用します!
更にDOMのアクセス可能になるまではダウンロードボタンを押せないようにuseEffect + useStateを用いて管理します!
useStateではBooleanを扱いbutton要素のdisabled属性にBooleanの値を追記します。
"use client"
import { QRCodeSVG } from "qrcode.react"
export function QrCodeDownloadButton({ profileIdPromise, clientRoleType }: Props) {
const [isReady, setIsReady] = useState(false)
const qrRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (qrRef.current?.querySelector("svg")) {
setIsReady(true)
}
}, [])
const url = "https://to-ko-s.com"
return (
<>
<button disabled={!isReady}>
QRコードダウンロード
</button>
{/* display:noneで隠しています */}
<div ref={qrRef} className="hidden">
<QRCodeSVG value={url} size={512} level="H" />
</div>
</>
)
}
これでQRコードがレンダリング後にダウンロードボタンを押せるようになりました!
ここからダウンロード機能の実装に入ります!
流れは下記になります!
- useRefでアクセスする
- Canvas要素の作成
- img要素を作成
- 作成したimg要素に取得したSVGの情報を付与する
- Canvasに「4」を描画させダウンロードする
ではコードの紹介です。
"use client"
import { QRCodeSVG } from "qrcode.react"
export function QrCodeDownloadButton({ profileIdPromise, clientRoleType }: Props) {
const [isReady, setIsReady] = useState(false)
const qrRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (qrRef.current?.querySelector("svg")) {
setIsReady(true)
}
}, [])
const url = "https://to-ko-s.com"
const downloadQrCode = () => {
// 1.useRefでアクセス
const svg = qrRef.current?.querySelector("svg")
if (!svg) return
// 2.Canvas要素の作成 + Canvas要素ののサイズ指定
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
if (!ctx) return
const pixelRatio = window.devicePixelRatio || 1
canvas.width = svg.clientWidth * pixelRatio
canvas.height = svg.clientHeight * pixelRatio
// 3.img要素を作成
const img = new Image()
// 4.作成したimg要素に取得したSVGの情報を付与する
const svgData = new XMLSerializer().serializeToString(svg)
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`
// 5.Canvasに「4」を描画させダウンロードする
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
//pngデータをを指定、jpgにしたい場合はimage/jpgになる
const pngUrl = canvas.toDataURL("image/png")
const link = document.createElement("a")
link.href = pngUrl
link.download = "qr-code.png"
link.click()
}
}
return (
<>
<button disabled={!isReady}>
QRコードダウンロード
</button>
{/* display:noneで隠しています */}
<div ref={qrRef} className="hidden">
<QRCodeSVG value={url} size={512} level="H" />
</div>
</>
)
}
別途「4」の別途解説
- new XMLSerializer().serializeToString(svg)について
- 取得したSVGデータをXML文字列に変換しています。
- new XMLSerializer()でインスタンスの作成、.serializeToString(svg)メソッドを呼び出し、SVG要素をXML形式の文字列に変換しています!
- img.src = data:image/svg+xml;base64,${btoa(svgData)}
- データURL(data:スキーム)は、ファイルの内容を直接URLに埋め込む方法になります!
通常のURLがリソースの「場所」を指定するのに対し、データURLはリソースの「内容」自体を含みます。 - btoa()はJavaScriptの組み込み関数で、文字列をBase64形式にエンコードします。Base64エンコードは、バイナリデータやテキストデータを、ASCII文字のみを使用して表現するための方法になります!
- data:image/svg+xml;base64,${btoa(svgData)}の処理
- btoa(svgData)で、SVGのXML文字列をBase64エンコード
- data:image/svg+xml;base64,${エンコードされたデータ} という形式のデータURLを作成
- data
- データURLであることを示すプロトコル識別
- image/svg+xml
- MIMEタイプ(コンテンツの種類がSVG画像であることを示す)
- base64
- エンコード方式がBase64であることを示す
- ,
- ヘッダーとデータを区切る区切り文字
- ${btoa(svgData)}
- Base64エンコードされたSVGデータ
- data
- データURL(data:スキーム)は、ファイルの内容を直接URLに埋め込む方法になります!
一見何してんだ?となりますが、分解すれば理解できると思います!
最終的なコード
"use client"
import { QRCodeSVG } from "qrcode.react"
export function QrCodeDownloadButton({ profileIdPromise, clientRoleType }: Props) {
~~~~ 省略 ~~~~
const downloadQrCode = () => {
// 1.useRefでアクセス
const svg = qrRef.current?.querySelector("svg")
if (!svg) return
// 2.Canvas要素の作成 + Canvas要素ののサイズ指定
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
if (!ctx) return
const pixelRatio = window.devicePixelRatio || 1
canvas.width = svg.clientWidth * pixelRatio
canvas.height = svg.clientHeight * pixelRatio
// 3.img要素を作成
const img = new Image()
// 4.作成したimg要素に取得したSVGの情報を付与する
const svgData = new XMLSerializer().serializeToString(svg)
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`
// 5.Canvasに「4」を描画させダウンロードする
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
//pngデータをを指定、jpgにしたい場合はimage/jpgになる
const pngUrl = canvas.toDataURL("image/png")
const link = document.createElement("a")
link.href = pngUrl
//qe-code.pngとしてダウンロード
link.download = "qr-code.png"
link.click()
}
}
return (
<>
<button disabled={!isReady}>
QRコードダウンロード
</button>
{/* display:noneで隠しています */}
<div ref={qrRef} className="hidden">
<QRCodeSVG value={url} size={512} level="H" />
</div>
</>
)
}
さいごに
今回はReactでQRコードのダウンロードボタンを作成しました!
url部分をPropsで受け取れば汎用的なコンポーネントになるかと思います!
blobデータを扱う際は理解していないと何をしているかわからなくなるためblobデータとはなにかから学ぶいいきっかけになりました!