【Web API】加速度センサーの情報を取得する 2025年版【DeviceMotionEvent】
まとめ
- 加速度センサーの情報はDeviceMotionEventインターフェースを介して取得できる
- iOS系では、
requestPermission()
を呼び出す必要がある - iOS系では、さらに各加速度の正負が反転している
私たちOrchestra Canvas Tokyoは、都内を中心に活動するアマチュア・オーケストラです。
次回は2025年7月にシューマンの交響曲第2番。
初めての方も、そうでない方も、お気軽にお越しください!詳しくはチケット販売ページまで。
背景
当団(Orchestra Canvas Tokyo)では、エイプリルフールとして、「Orchestra "Nyan"vas Tokyo」への改名企画を行いました。
インタラクティブなコンテンツとして、物理演算を用いて肉球で遊べるウェブページを作成しました。
前回の記事では、物理演算周りの実装についてまとめました。
今回は、加速度センサーに関する実装について、知見をまとめていきます!
加速度センターの値を取得する
accelerationIncludingGravity
プロパティを使うことで、取得できます。
デバイスが重力の影響を除くことに対応している場合、acceleration
プロパティを使うことも可能です。
また、Windowインターフェースに用意されている、deviceMotion
イベントを利用することで、変化した値を随時取得できます。
window.addEventListener('devicemotion', (e: DeviceMotionEvent) => {
if (
!e.accelerationIncludingGravity ||
!e.accelerationIncludingGravity.x ||
!e.accelerationIncludingGravity.y ||
!e.accelerationIncludingGravity.z
)
return;
// 重力加速度
const gx = e.accelerationIncludingGravity.x;
const gy = e.accelerationIncludingGravity.y;
const gz = e.accelerationIncludingGravity.z;
const acceleration = [gx, gy, gz];
doSomething(acceleration)
})
画面の描画方向との整合性をとる
先の方法で取得される加速度は、端末の方角と対応したものです。
具体的には、次のような対応関係になっています。
- X軸:西から東へ
- Y軸:南から北へ
- Z軸:下から上へ
一般的なスマホでいえば、インカメラが北側、ホームバーやナビゲーションメニューが南側にあります。
この「方角」は、画面の方向では変わりません。
そのため、画面の方向をwindow.screen.orientation.type
で取得して、調整する必要があります。
とある情報筋によると、一部のiPadなどではもう少し込み入った考慮が必要なようですが……
今回は目をつむることにしたいと思います。
以上の情報をもとに、先のコードを修正します。
window.addEventListener('devicemotion', (e: DeviceMotionEvent) => {
if (
!e.accelerationIncludingGravity ||
!e.accelerationIncludingGravity.x ||
!e.accelerationIncludingGravity.y ||
!e.accelerationIncludingGravity.z
)
return;
// 重力加速度
const gx = e.accelerationIncludingGravity.x;
const gy = e.accelerationIncludingGravity.y;
const gz = e.accelerationIncludingGravity.z;
let acceleration: [number, number, number]
// 傾きに応じて重力を調節
switch (window.screen.orientation.type) {
case 'landscape-primary':
// 横長
acceleration = [gy, gx, gx];
break;
case 'landscape-secondary':
// 横長逆転
acceleration = [-gy, -gx, gz];
break;
case 'portrait-secondary':
// 縦長逆転
acceleration = [gx, -gy, gz];
break;
default: // case 'portrait-primary'
// 縦長
acceleration = [-gx, gy, gz];
break;
}
return acceleration;
})
iOS系では加速度を反転させる
iOS系(iOSやiPadOS)では、仕様と加速度の方向が異なっており、この反転作業を行う必要があります。
次に、iOS系を検出する方法が問題となります。
ここでは、後述するrequestPermission()
メソッドを有する場合をiOS系として判定します。
window.addEventListener('devicemotion', (e: DeviceMotionEvent) => {
if (
!e.accelerationIncludingGravity ||
!e.accelerationIncludingGravity.x ||
!e.accelerationIncludingGravity.y ||
!e.accelerationIncludingGravity.z
)
return;
// 重力加速度
const gx = e.accelerationIncludingGravity.x;
const gy = e.accelerationIncludingGravity.y;
const gz = e.accelerationIncludingGravity.z;
let acceleration: [number, number, number]
// 傾きに応じて重力を調節
switch (window.screen.orientation.type) {
case 'landscape-primary':
// 横長
acceleration = [gy, gx, gx];
break;
case 'landscape-secondary':
// 横長逆転
acceleration = [-gy, -gx, gz];
break;
case 'portrait-secondary':
// 縦長逆転
acceleration = [gx, -gy, gz];
break;
default: // case 'portrait-primary'
// 縦長
acceleration = [-gx, gy, gz];
break;
}
const unknownDeviceMotionEvent = e as unknown;
const isWithRequestPermission =
typeof unknownDeviceMotionEvent === 'function' &&
unknownDeviceMotionEvent !== null &&
'requestPermission' in unknownDeviceMotionEvent &&
typeof unknownDeviceMotionEvent.requestPermission === 'function';
const isIOS = isWithRequestPermission;
if (isIOS)
acceleration = [-acceleration[0], -acceleration[1], -acceleration[2]];
return acceleration
})
acceleration.map(a => -a)
と実装すると、正しく[number, number, number]
に型推論されないため、上記のような実装としています。
iOS系でセンサー仕様の許可を得る
ユーザーのインタラクション(ボタンのクリックイベントなど)をもとに、DeviceMotionEvent.requestPermission()
を呼び出す必要があります。
加速度センターはなかなかの情報量を有するので、こうなるのは仕方ないですね。
仕様の整理
requestPermission()
メソッドの仕様を整理します。
主な仕様は次の通りです。
- iOS系で用意されている静的メソッド
- その他のブラウザでは用意されておらず、TypeScriptでは型定義もされていない
-
Promise<'granted' | 'denied'>
を返す- ユーザーが許諾したら
granted
、拒否したらdenied
を返す
- ユーザーが許諾したら
- ユーザのインタラクションなしに呼び出した場合
- すでに許諾されている場合(最近許諾されているなど?)、
granted
を返す - 許諾されていない場合、rejectされる
- すでに許諾されている場合(最近許諾されているなど?)、
実装
今回は、次のようなコードで実装しました。
Svelte(Kit)で実装しています。
特に、requestPermission()
メソッドはiOS系以外には実装されておらず、TypeScriptの型定義上も用意されていません。
any型にキャストしてしまうのも手ですが、今回は丁寧に記述してみました。
export class DeviceMotionController {
readonly isWithRequestPermission: boolean;
readonly updatePermissionStatusCallback: (permitted: boolean) => void;
/**
* コンストラクタ
* @param updatePermissionStatusCallback
* 加速度センサーにアクセスするためのパーミッション状態が変更されたときに呼び出されるコールバック。
* このコールバックは、`DeviceMotionController` のパーミッション状態が変更されたときに呼び出されます。
*/
constructor(updatePermissionStatusCallback: typeof this.updatePermissionStatusCallback) {
this.updatePermissionStatusCallback = updatePermissionStatusCallback;
const unknownDeviceMotionEvent = window.DeviceMotionEvent as unknown;
this.isWithRequestPermission =
typeof unknownDeviceMotionEvent === 'function' &&
unknownDeviceMotionEvent !== null &&
'requestPermission' in unknownDeviceMotionEvent &&
typeof unknownDeviceMotionEvent.requestPermission === 'function';
}
/**
* 加速度センサーにアクセスするためのパーミッションを要求します。
* iOS では、`DeviceMotionEvent.requestPermission()` を呼び出して
* パーミッション状態を変更することができます。
*/
requestPermission(): void {
const deviceMotionEvent = window.DeviceMotionEvent as unknown;
// 型定義、型ガード関数を用意する
type deviceMotionEventWithRequestPermission = typeof window.DeviceMotionEvent & {
requestPermission: () => Promise<'granted' | 'denied'>;
};
const isDeviceMotionEventWithRequestPermission = (
unknownDeviceMotionEvent: unknown
): unknownDeviceMotionEvent is deviceMotionEventWithRequestPermission =>
this.isWithRequestPermission;
if (!isDeviceMotionEventWithRequestPermission(deviceMotionEvent)) {
// 許可取得が不要な環境
return;
}
unknownDeviceMotionEvent
.requestPermission()
.then((permissionState) => {
this.updatePermissionStatusCallback(permissionState === 'granted');
})
.catch(() => {
// ユーザーのインタラクションに依らない許可申請
// かつ、許可されていない場合
this.updatePermissionStatusCallback(false);
});
}
}
<script lang="ts">
import { afterNavigate } from '$app/navigation';
import { DeviceMotionController } from './nyanvas/DeviceMotionController';
let deviceMotionController: DeviceMotionController | null = null;
let showPermissionToast = $state(false);
afterNavigate(() => {
const updatePermissionStatusCallback = (permitted: boolean) => {
showPermissionToast = !permitted;
};
deviceMotionController = new DeviceMotionController(updatePermissionStatusCallback);
// 一回呼び出しし、ユーザーの許可状態を確認する
deviceMotionController.requestPermission();
});
const onclickGrantPermission = () => {
if (!deviceMotionController) return;
deviceMotionController.requestPermission();
showPermissionToast = false;
};
</script>
<div class="toast" class:show={showPermissionToast}>
<p>ぜひ、加速度センサー付きでご覧ください!</p>
<button onclick={onclickGrantPermission}>進む</button>
</div>
<style>
/* 省略 */
</style>
おわりに
ここまでお読みいただきありがとうございました!
やはり、「目に見えて動くものを作る」というのはプログラミング欲の原点たる悦びがありますね。
環境間の実装差異をコードで吸収する作業も、IE時代を思い出してちょっとエモかったですね~
……これぐらいの実装差異で済んでいるからかもしれませんが。
次回演奏会のご案内
Orchestra Canvas Tokyoは、都内を中心に活動するアマチュアオーケストラです。
日々の癒しに、新たなひらめきのきっかけに——
オーケストラの演奏会はいかがでしょうか?
初めての方も大歓迎!
ご来場をお待ちしています。
Orchestra Canvas Tokyo
第14回定期演奏会2025年7月12日(土)
練馬区立練馬文化センター 大ホール
シューマン / 交響曲第2番 ほか詳細はチケット販売ページにて
Discussion