見出し画像

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 にアクセスすると以下のページが表示されます。

画像1

これでアプリの基本とはできました。

見た目を整える

方針
見た目の方針は

• スマホで使用すること
• 揃えた時に嬉しくなること

上記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;
}

これでページを確認すると以下のように表示されます。

画像2

スマホ向けに調整
このままでは、スマホで見た時に画像が全て表示されなかったり、余分な点が残っていたりするので調整していきます。
調整は、http://localhost:4200 にアクセスして、Chrome DevTools を使って調整していきます。

画像3

調整が完了したら 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;
 }
}

ここまでで以下のように表示されます。

画像4

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 にします。

画像5

次に、ページにアクセスできるようにします。下記の様な方法がありますが、やりやすい物を選択してください。私は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アプリの幅の広がりを感じました。

参考にした・素材を頂いたサイト



この記事が気に入ったらサポートをしてみませんか?