予定をQRコードで配る:iCalendar(VEVENT)をブラウザだけで生成しqrcode.jsでQR化する

イベントの告知チラシやPOPに「読み取るだけでカレンダーに予定が入るQR」を載せたい、という要望はよくある。仕組みは単純で、QRコードに iCalendar(VEVENT)形式のテキストを埋め込むだけだ。スマホのカメラで読み取ると、日時・場所・タイトルが入力されたカレンダー登録画面が開く。

この処理はすべてクライアント側(ブラウザ)で完結できる。サーバーもビルドも要らない。自分のツールサイトで実際に動かしている実装(予定QRコード生成ツール)をもとに、要点を切り出して解説する。

埋め込むテキストの正体

QRに入れるのは、拡張子 .ics でおなじみの iCalendar テキストそのものだ。最小構成はこうなる。

1
2
3
4
5
6
7
8
9
10
11
12
13
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//example//calqr//JA
BEGIN:VEVENT
UID:20260817T010000Z-demo@example.com
DTSTAMP:20260817T010000Z
SUMMARY:夏祭り 実行委員会
DTSTART:20260801T180000
DTEND:20260801T200000
LOCATION:市民ホール 大会議室
DESCRIPTION:出欠を事前にご連絡ください。
END:VEVENT
END:VCALENDAR

ポイントは2つある。行区切りは CRLF(\r\nであること、そして SUMMARY などのテキスト値はエスケープが必要なことだ。

テキスト値のエスケープ

iCalendar(RFC 5545)では、テキスト値の中の \;,・改行をエスケープしなければならない。これを怠ると、カンマを含むタイトルなどで予定が途中で切れる。

1
2
3
4
5
6
7
function esc(s) {
return String(s || '')
.replace(/\\/g, '\\\\') // バックスラッシュを先に
.replace(/;/g, '\\;')
.replace(/,/g, '\\,')
.replace(/\r?\n/g, '\\n');
}

バックスラッシュの置換を最初にやるのが肝心だ。順番を間違えると、後から足したエスケープ用の \ を二重にエスケープしてしまう。

日時のフォーマット

<input type="datetime-local"> の値は 2026-08-01T18:00 の形で得られる。これを iCalendar の YYYYMMDDTHHMMSS に変換する。タイムゾーンを付けない「フローティング時刻」にしておくと、読み取った端末のローカル時刻として素直に扱われる。

1
2
3
4
5
function fmtDateTime(v) {
var m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(v || '');
if (!m) return null;
return m[1] + m[2] + m[3] + 'T' + m[4] + m[5] + '00';
}

終日イベントにするなら DTSTART;VALUE=DATE:YYYYMMDD と日付だけを入れる。

VEVENT を組み立てる

配列に行を積んで \r\n で結合するのが安全だ。文字列連結より、条件付きで行を足したいときに読みやすい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function buildICS(ev) {
var lines = [
'BEGIN:VCALENDAR', 'VERSION:2.0',
'PRODID:-//example//calqr//JA', 'BEGIN:VEVENT',
'UID:' + Date.now() + '-demo@example.com',
'SUMMARY:' + esc(ev.title),
'DTSTART:' + fmtDateTime(ev.start),
'DTEND:' + fmtDateTime(ev.end),
];
if (ev.location) lines.push('LOCATION:' + esc(ev.location));
if (ev.description) lines.push('DESCRIPTION:' + esc(ev.description));
lines.push('END:VEVENT', 'END:VCALENDAR');
return lines.join('\r\n');
}

qrcode.js でQR化する

あとは組み立てたテキストを qrcode.jsqrcode パッケージのブラウザビルド)に渡すだけだ。CDNから読み込めば依存はこれ1つで済む。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<canvas id="qr"></canvas>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>
<script>
var text = buildICS({
title: '夏祭り 実行委員会',
start: '2026-08-01T18:00',
end: '2026-08-01T20:00',
location: '市民ホール 大会議室',
description: '出欠を事前にご連絡ください。'
});
QRCode.toCanvas(document.getElementById('qr'), text, {
errorCorrectionLevel: 'M', margin: 4, width: 360
});
</script>

実務での注意点

  • 情報量を増やしすぎない。予定のテキストが長いほどQRは複雑(高密度)になり、読み取りづらくなる。タイトルと説明は簡潔に。
  • 誤り訂正レベルとサイズはトレードオフ。データ量が多い予定QRでレベルを上げると、さらに密になる。印刷は大きめのサイズで。
  • 端末差。読み取り後に「登録画面が開く」までで、保存はユーザー操作が必要。カメラアプリやカレンダーアプリによって挙動が変わる。

入力からQR生成・PNG/SVGダウンロードまでを実際に試せるツールを公開している。動きを確認したい場合はこちらへ。

VEVENT はテキストの組み立てとエスケープさえ押さえれば、サーバーレスで完結できる手軽なフォーマットだ。連絡先(vCard)やWi-Fi設定のQRも同じ発想で作れる。


予定をQRコードで配る:iCalendar(VEVENT)をブラウザだけで生成しqrcode.jsでQR化する
https://blog.hashito.biz/2026/07/17/ical-vevent-qr-code-clientside-qrcodejs/
著者
hashito
作成日
2026年7月17日
著作権