WebNFCでスタンプラリーを作ってみよう
はじめに
こんにちは、co-meetingの町田(@mcho71)です。
2019年12月にChromeでNFCのAPIがβ公開され、Chrome上で動くWebアプリでもNFCを使うことができるようになりました。 (β版のため、この記事の情報も古くなっていて、動かないことがあるかもしれません🙏)
今回は、このWeb NFCを使ってスタンプラリーを作ってみたいと思います。
この記事の成果物など
成果物を見たほうが、作業を進める上でイメージが湧きやすいと思いますので、先に置いておきます。また、時間がない人のためにソースへのリンク等も置いておきます。
• デモページは Web NFCでスタンプラリーのデモページ に公開しています。以下の動画でアプリを動かしている所が見れます。
• アプリのソースは mcho71/nfc-stamp-rally にあります。
• Web NFCの実装は、Web NFCを使ってスタンプを押せるようにするで行っています。
今回使うもの
• Angular : JSフレームワーク、このアプリの大枠を作ります。
• Angular CLI : Angularの便利なコマンドラインツールです。
• Web NFC API : NFCの読み書きを行います。
• Chromeブラウザ : アプリの動作プラットフォームです。
• NFC対応のAndroidのスマホ : アプリの動作確認に使います。
• NFCチップ搭載カード : スタンプの代わりになるカードです。交通系ICカード等でも可です。
• Mac book : 開発用のPCです
どんなアプリにしたいか
通常のスタンプラリーとしても、また、家の中にカードを隠して宝探しゲームとしても使えるアプリにしたいと思いました。そのために、以下を目標に作りました。
• スマホで使える
• かざすだけでスタンプを押せる。
• 完成したら嬉しくなるようにする。
• 何度もプレイできる。
アプリを作っていきます
AngularCLIでアプリの作成
なにはともあれアプリの作成です。
AngularCLIを使用することでアプリの作成や開発がスムーズに行えますのでインストールします。
AngularCLIにはNodeJSが必要なので合わせてインストールします。私はHomebrewを使ってインストールしました。
Angular CLIをインストールします。
ngコマンドが実行できるようになったら用意完了です。続いて以下のコマンドを実行します。
> ng new nfc-stamp-rally -s --style css -S --strict --routing false
うまくいけば、`nfc-stamp-rally`フォルダと、その中にアプリのファイルが生成されます。以下のコマンドを実行後、 http://localhost:4200 にアクセスするとAngularのスタートページが表示されます。
cd nfc-stamp-rally
npm run start
ページの基本を作る
アプリ作成時点で、Angularのスタートぺージは nfc-stamp-rally/src/app/app.component.html に書かれています、このファイルを以下のように書き換えます。
// nfc-stamp-rally/src/app/app.component.html
<h1>
{{ title }}
</h1>
<ul>
<li *ngFor="let stamp of stamps">
{{ stamp.name }}
</li>
</ul>
次にスタンプのデータを保持できるように nfc-stamp-rally/src/app/app.component.ts を書き換えます。
// nfc-stamp-rally/src/app/app.component.ts
import { Component } from '@angular/core';
type Stamp = {
name: string,
imagePath: string
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'nfc-stamp-rally';
stamps: Stamp[] = [];
constructor() {
// 確認のため、データを詰めて置きます。
this.stamps.push({ name: 'testName', imagePath: 'test' });
}
}
ここまでを保存して、http://localhost:4200 にアクセスすると以下のページが表示されます。
これでアプリの基本とはできました。
見た目を整える
方針
見た目の方針は
• スマホで使用すること
• 揃えた時に嬉しくなること
上記2点を実現するために
• スタンプとなる画像が縦に連なって見える
• スタンプはなるべく横幅いっぱいに広がる
ことを意識しました。
画像の用意
デザインを当てるにあたってスタンプ用の画像を用意し、上から下に三分割して nfc-stamp-rally/src/assets/stamps に 1.png, 2.png, 3.png として配置しました。
次に、画像をアプリで表示するために html, ts ファイルを編集します。
// nfc-stamp-rally/src/app/app.component.html
<h1>
{{ title }}
</h1>
<ul>
<li *ngFor="let stamp of stamps">
<img [src]="stamp.imagePath" [alt]="stamp.name">
</li>
</ul>
// nfc-stamp-rally/src/app/app.component.ts
import { Component } from '@angular/core';
const STAMPS = [
{
name: 'head',
imagePath: 'assets/stamps/1.png'
},
{
name: 'neck',
imagePath: 'assets/stamps/2.png'
},
{
name: 'leg',
imagePath: 'assets/stamps/3.png'
}
] as const;
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'nfc-stamp-rally';
stamps = STAMPS;
}
これでページを確認すると以下のように表示されます。
スマホ向けに調整
このままでは、スマホで見た時に画像が全て表示されなかったり、余分な点が残っていたりするので調整していきます。
調整は、http://localhost:4200 にアクセスして、Chrome DevTools を使って調整していきます。
調整が完了したら nfc-stamp-rally/src/styles.css に追記していきます。
// nfc-stamp-rally/src/styles.css
ul.stamp-list {
list-style: none;
margin: 0;
padding: 0;
}
ul.stamp-list > li {
height: 59.5vw;
}
ul.stamp-list > li > img {
width: 100%;
}
ul.stamp-list > li > .stamp-incorrect {
display: flex;
justify-content: center;
align-items: center;
height: calc(100% - 20px);
margin: 10px;
font-size: 2rem;
color: #FFCEAA;
border: 4px solid #FFF0DD;
border-radius: 3em .8em 3em .7em/.9em 2em .8em 3em;
}
スタイルの確認のために ts ファイルを変更します。
// nfc-stamp-rally/src/app/app.component.ts
import { Component } from '@angular/core';
const STAMPS = [
{
name: 'head',
imagePath: 'assets/stamps/1.png'
},
{
name: 'neck',
imagePath: 'assets/stamps/2.png'
},
{
name: 'leg',
imagePath: 'assets/stamps/3.png'
}
] as const;
type Stamp = typeof STAMPS[number];
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'nfc-stamp-rally';
stamps = STAMPS.map(stamp => {
return {
...stamp,
correct: false
}
});
constructor() {
// デザイン調整をしやすくするために押された状態を作る
this.stamps[0].correct = true;
this.stamps[2].correct = true;
}
}
ここまでで以下のように表示されます。
Web NFCを使ってスタンプを押せるようにする
Web NFCを使って、NFCカードをかざした時に、それぞれ対応したスタンプが押されるように実装していきます。
実装を始める前にWeb NFCの型定義が必要になるため、web-nfc.d.ts - Web NFCの型定義 を、nfc-stamp-rally/src/web-nfc.d.ts に配置します。
実装の準備はできたので、以下のコードを参考に実装していきます。
最終的に ts, html, cssファイルを追記し、以下のようなコードになりました。実際に使う場合はNFCカードのシリアルナンバーを書き換える必要があります。その場合は、Web NFC Sample などを使ってシリアルコードを調べ、書き換えてください。
// nfc-stamp-rally/src/app/app.component.ts
import { Component } from '@angular/core';
const STAMPS = [
{
name: 'head',
imagePath: 'assets/stamps/1.png'
},
{
name: 'neck',
imagePath: 'assets/stamps/2.png'
},
{
name: 'leg',
imagePath: 'assets/stamps/3.png'
}
] as const;
const NFCCardSerialNumberStampNameMap = new Map<string, typeof STAMPS[number]['name']>([
['0', 'head'],
['1', 'neck'],
['2', 'leg']
]);
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'nfc-stamp-rally';
stamps = STAMPS.map(stamp => {
return {
...stamp,
correct: false
}
});
scanning = false;
ndef?: NDEFReader;
resetStamp() {
for (const stamp of this.stamps) {
stamp.correct = false;
}
}
async start() {
if (!this.scanning && !await this.startScan()) {
return;
}
try {
// @ts-ignore
this.ndef!.addEventListener("reading", ({ serialNumber }) => {
const stampName = NFCCardSerialNumberStampNameMap.get(serialNumber);
// @ts-ignore
const stamp = this.stamps.find(stamp => stamp.name === stampName);
if (stamp) {
stamp.correct = true;
}
});
} catch (error) {
alert(`読み取りエラー: ${error}`);
}
}
async startScan() {
if (!('NDEFReader' in window)) {
alert('Web NFCを有効にしてください。');
return false;
}
this.ndef = new NDEFReader();
await this.ndef.scan();
this.scanning = true;
return true;
}
}
// nfc-stamp-rally/src/app/app.component.html
<h1>
{{ title }}
</h1>
<button (click)="scanning ? resetStamp() : start()" [class]="scanning ? 'reset-button' : 'start-button'">
{{ scanning ? '最初から始める' : 'スタート' }}
</button>
<ul class="stamp-list">
<li *ngFor="let stamp of stamps">
<img *ngIf="stamp.correct; else elseTemplate" [src]="stamp.imagePath" [alt]="stamp.name">
<ng-template #elseTemplate>
<div class="stamp-incorrect">
スタンプ
</div>
</ng-template>
</li>
</ul>
// nfc-stamp-rally/src/styles.css
---省略しています---
button {
width: 100%;
margin: 20px 0;
padding: 5px 30px;
border: none;
border-radius: 12px;
text-decoration: none!important;
font-size: 2.5rem;
color: #fff;
}
button.start-button {
background-color: #eb6100;
}
button.reset-button {
background-color: #55eeff;
}
button:hover,
button:hover {
opacity: 0.8;
}
動作確認
動作確認するためには、NFC搭載のAndroidスマホで、Web NFCが有効になったChromeブラウザから、ページを開く必要があります。
まずは、Web NFCを有効にするためにAndroidスマホのChromeブラウザで、アドレスバーから chrome://flags にアクセスして、Experimental Web Platform features を Enabled にします。
次に、ページにアクセスできるようにします。下記の様な方法がありますが、やりやすい物を選択してください。私はGitHub Pagesに公開してテストしました。
• localhostをLAN内に公開してAndroidスマホからアクセスする。
• Androidの端末からPCのローカルサーバにアクセスする
• ホスティングサービスを使ってWebへ公開する。例) GitHubPages, Netlify, Firebase Hosting等々
• ngrokを使う。ngrokが便利すぎる - Qiita
Web NFCが対応しているタグのカードがない場合
交通系ICカードや電子マネー系のタグはWeb NFCが対応していません。対応しているタグを持ったカードは、なかなか持っていないと思います、なので対応していないカードでも動くようにします。
対応していないカードの場合でも、読み取りエラーになった事は検知できます。なので、読み取りエラーとなった場合でもスタンプを押すようにすれば良いはずです。
具体的には読み取りエラー時に、スタンプを上から順番に押すようにします。
start メソッド内の this.ndef.addEventListener("reading"... している箇所の前に下記を追記します。
// nfc-stamp-rally/src/app/app.component.ts
...
try {
let errorCount = 0;
this.ndef!.addEventListener("error", () => {
if (errorCount >= this.stamps.length) {
return;
}
this.stamps[errorCount].correct = true;
errorCount++;
});
// @ts-ignore
this.ndef!.addEventListener("reading", ({ serialNumber }) => { ...
これでWeb NFCが対応していないタグを持ったカードでも遊べるようになりました。
完成
動画にしてあげてみました。
デモページは Web NFCでスタンプラリーのデモページ に公開しています。以下の動画でアプリを動かしている所が見れます。
次の動画ではWeb NFCが対応していないNFCカードで動かしています。
おわりに
WebでもNFCが使えるようになるというニュースを見た時は驚きでした。実際に使ってみると、簡単に動き、Webアプリの幅の広がりを感じました。
参考にした・素材を頂いたサイト
この記事が気に入ったらサポートをしてみませんか?