本記事はクソアプリ Advent Calendar 2024の4日目の記事です。
クソアプリAdvent Calendarに参加してみたかったので作りました。手ブレ写真が撮れるウェブアプリです。何の役にも立ちません。
今回作ったアプリ
「Shake Snap」と名付けました。
デバイスを振ることで写真を撮影できます。スマートフォンでの利用を想定しています。スマートフォンでなくとも加速度センサーを内蔵しているデバイスであれば利用できると思いますが、検証は手元のiPhone 12 miniでのみ行っています。
アプリのURL:
ソースコード:
使用した技術
Viteを使ってサクッと作りました。テンプレートはvanilla-tsです。始めはCSSをスクラッチで書いていたのですが、途中から書くのが面倒になったのでTailwind CSSを導入しました。
コア機能の解説
ここではアプリのコア機能である次の2点について簡単に説明します。
- カメラを使った写真の撮影
- デバイスの振動判定
カメラを使った写真の撮影
デバイスのカメラ利用は、mediaDevices.getUserMedia()
を使いました。このメソッドでカメラから映像ストリームを取得し、それを<video>
要素に流し込んでいます。
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
audio: false,
});
this.#videoElement.srcObject = stream;
this.#onCameraStart();
return true;
} catch (error) {
console.error(error);
alert(
'カメラの起動に失敗しました。カメラへのアクセスを許可してください。'
);
}
写真の撮影は<canvas>
を使っています。<video>
の現在のフレームをキャプチャして、toDataURL()
で画像をデータURLに変換しています。
capture(ctx: CanvasRenderingContext2D): string {
const canvas = ctx.canvas;
canvas.width = this.#videoElement.videoWidth;
canvas.height = this.#videoElement.videoHeight;
ctx.drawImage(this.#videoElement, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL('image/jpeg');
}
デバイスの振動判定
デバイスの振動検出ではDeviceMotionEvent
を利用しました。
acceleration
の値を取得し、X・Y・Z軸方向の加速度を基に、振動の強さを計算しています。計算のロジックは単純で、加速度ベクトルの大きさを求めているだけです。
振動の強さが閾値を超えた場合、写真の撮影をトリガーします。
const handler = (event: DeviceMotionEvent) => {
const { acceleration } = event;
const x = acceleration?.x ?? 0;
const y = acceleration?.y ?? 0;
const z = acceleration?.z ?? 0;
const totalAcceleration = Math.sqrt(x ** 2 + y ** 2 + z ** 2);
this.#onShake(totalAcceleration);
if (totalAcceleration > 0) {
this.#isShaken = true;
}
};
window.addEventListener(
'devicemotion',
event => {
requestAnimationFrame(() => handler(event));
},
{
passive: true,
}
);
また現状のiOSデバイス(iOS13以降?)ではDeviceDeviceMotionEvent
を使用するためにユーザーへの権限のリクエストが必要です。つまり明示的にDeviceMotionEvent.requestPermission()
を実行する必要があります。
#getRequestPermission() {
const requestPermission = (
DeviceMotionEvent as unknown as DeviceMotionEventIOS
).requestPermission;
const isRequestNeeded = typeof requestPermission === 'function';
if (!isRequestNeeded) {
return async () => 'granted' as const;
}
return requestPermission;
}
async requestPermission(): Promise<boolean> {
const requestPermission = this.#getRequestPermission();
try {
const permissionState = await requestPermission();
if (permissionState === 'granted') {
this.#onPermissionGranted();
this.#registerEventHandlers();
return true;
} else {
alert('加速度センサーの許可が得られませんでした');
console.warn('permissionState:', permissionState);
}
} catch (error) {
console.error(error);
}
return false;
}
以上の流れで、デバイスの振動を検知し、写真の撮影を行っています。
おわりに
クソアプリAdvent Calendarには今回はじめて参加したこともあり、楽しみながら実装できました。アイディア出しはAIに手伝ってもらいました。
短い期間で役に立たないアプリを作ることに焦点を当てていため、技術的な深掘りをほとんどできなかったことが今回の反省点です。もしまた参加する機会があれば、技術的な工夫も取り入れながら取り組みたいと思います。