【翻訳】ネイティブアプリのような使い心地を実現するJavascript API 4選(Juan Diego Rodríguez, Smashing Magazine)

www.smashingmagazine.com

概要:Screen Orientation APIをご存知ですか?Device Orientation API、Vibration API、Contact Picker APIについてはどうでしょう?Juan Diego Rodriguezは、あまり知られていないウェブ機能に興味を持ち、これらの機能が広くサポートされるようになった場合に、より使いやすく堅牢なプログレッシブウェブアプリを作成するためにどのように活用できるかを考察しています。

2年ほど前、JavaScriptの現状に関する調査で、認知度が最も低かった4つのJavaScript APIがありました。私は、これらのAPIが有用である可能性を秘めているにもかかわらず、正当な評価を受けていないことに興味を持ちました。ざっと検索しただけでも、ECMAScript 仕様で追加された新しいウェブ API のうち、正当な評価を受けていないものや、ブラウザでの認知度やサポートが十分でないものが非常に多いことに驚きました。

このような状況は「ジレンマ」になりかねません。

これらの API の大半は、プログレッシブウェブアプリ(PWA)を強化し、ウェブアプリとネイティブアプリのギャップを埋めるために設計されています。PWA を作成するには、マニフェストファイルを追加する以上の作業が必要であることを覚えておいてください。確かに、定義上は PWA ですが、実際にはホーム画面上のブックマークのような機能を持っています。実際には、ウェブ上で完全にネイティブアプリのような体験を実現するには、いくつかのAPIが必要です。そして、私がご紹介したい4つのAPIは、かつてネイティブアプリでのみ可能だと思われていた機能をウェブにもたらすPWAパズルのピースの一部です。

これらの API はすべて、このデモで実際にご覧いただけます。

1. Screen Orientation API

Screen Orientation API を使用すると、デバイスの現在の向きを検知することができます。ユーザーが縦向きまたは横向きで閲覧しているかが分かれば、それに応じて UI を変更することで、モバイルデバイスのユーザーエクスペリエンスを向上することができます。また、画面を特定の位置で固定するために使用することもできます。これは、広いビューポートが有効な動画やその他のフルスクリーン要素を表示する際に便利です。

グローバルなscreenオブジェクトを使用すると、screen.orientationオブジェクトを含め、ページをレンダリングするために画面が使用するさまざまなプロパティにアクセスできます。このオブジェクトには2つのプロパティがあります。

  • type: 現在の画面の方向。 "portrait-primary", "portrait-secondary", "landscape-primary", または "landscape-secondary" のいずれかが指定されます。
  • angle: 現在の画面の方向角。0度から360度の間の任意の数値ですが、通常は90度の倍数で設定されます(例: 0, 90, 180, または 270)。

モバイルデバイスでは、angle が 0 度の場合、type はほとんどの場合 "portrait"(縦)と評価されますが、デスクトップデバイスでは通常 "landscape"(横)と評価されます。これにより、type プロパティはデバイスの正確な位置を知るのに適したものとなります。

screen.orientationオブジェクトには、2つのメソッドもあります。

  • .lock(): これは非同期メソッドで、type 値を引数として取り、画面をロックします。
  • .unlock(): このメソッドは、画面をデフォルトの向きにロック解除します。

最後に、screen.orientation は、画面の方向が変更されたことを通知する "orientationchange" イベントをカウントします。

ブラウザサポート

画面の向きを検出して固定する

Screen Orientation API を使用して短いデモをコーディングし、デバイスの向きを把握し、現在の位置で固定する方法を見てみましょう。

これは、HTMLの雛形として使用できます。

<main>
  <p>
    方向タイプ: <span class="orientation-type"></span>
    <br />
    方向角度:<span class="orientation-angle"></span>
  </p>

  <button type="button" class="lock-button">画面ロック</button>

  <button type="button" class="unlock-button">ロック解除</button>

  <button type="button" class="fullscreen-button">フルスクリーン表示</button>
</main>

JavaScript 側では、画面の回転方向 type および angle プロパティを HTML に注入します。

let currentOrientationType = document.querySelector(".orientation-type");
let currentOrientationAngle = document.querySelector(".orientation-angle");

currentOrientationType.textContent = screen.orientation.type;
currentOrientationAngle.textContent = screen.orientation.angle;

これで、デバイスの向きと角度のプロパティを確認できます。私のラップトップでは、「landscape-primary」です。

ウィンドウの orientationchange イベントを監視すると、画面が回転するたびに値がどのように更新されるかがわかります。

window.addEventListener("orientationchange", () => {
  currentOrientationType.textContent = screen.orientation.type;
  currentOrientationAngle.textContent = screen.orientation.angle;
});

画面をロックするには、まずフルスクリーンモードにする必要があるので、もうひとつ非常に便利な機能であるフルスクリーンAPIを使用します。誰も、自分の意思に反してウェブページがフルスクリーンモードに切り替わることを望んでいません。そのため、DOM要素からの一時的な起動(つまり、ユーザーのクリック)がなければ機能しません。

フルスクリーン API には 2 つのメソッドがあります。

  1. Document.exitFullscreen() はグローバルドキュメントオブジェクトから使用されます。
  2. Element.requestFullscreen() は、指定された要素とその子孫をフルスクリーンにします。

ページ全体をフルスクリーン表示にしたいので、document.documentElement オブジェクトのルート要素からメソッドを呼び出します。

const fullscreenButton = document.querySelector(".fullscreen-button");

fullscreenButton.addEventListener("click", async () => {
  // すでにフルスクリーン表示になっている場合は、通常の表示に戻します。
  if (document.fullscreenElement) {
    await document.exitFullscreen();
  } else {
    await document.documentElement.requestFullscreen();
  
});

次に、現在の画面表示を固定します。

次に、現在の画面表示をロックします。

const lockButton = document.querySelector(".lock-button");

lockButton.addEventListener("click", async () => {
  try {
    await screen.orientation.lock(screen.orientation.type);
  } catch (error) {
    console.error(error);
  
});

そして、ロック解除ボタンではその逆を行います。

const unlockButton = document.querySelector(".unlock-button");

unlockButton.addEventListener("click", () => {
  screen.orientation.unlock();
});

メディアクエリで画面向きを確認することはできますか?

もちろん可能です。CSS メディアクエリの orientation メディア機能 を使用して、ページの向きを確認することができます。ただし、メディアクエリは、横向きの場合は「幅が縦幅よりも大きい」か、縦向きの場合は「幅が縦幅よりも小さい」かをチェックすることで、現在の向きを計算します。これに対し、

Instagram や X などの PWA が、ネイティブシステムの向きがロックされていない場合でも、画面を強制的に縦向きにするのに気づいたかもしれません。この動作は Screen Orientation API によって実現されているのではなく、manifest.json ファイルで orientation プロパティを望ましい向きの種類に設定することによって実現されていることに注意することが重要です。

2. Device Orientation API

次に紹介したい API は、Device Orientation API です。これは、デバイスジャイロセンサーにアクセスして、空間におけるデバイスの向きを読み取るための API です。主にゲームなどのモバイルアプリでは、常にこの API が使用されています。この API は、デバイスが動くたびにトリガーされる deviceorientation イベントでこれを実現します。この API には以下のプロパティがあります。

  • event.alpha: Z軸に沿った方向で、0度から360度の範囲です。
  • event.beta: X軸に沿った方向で、-180度から180度の範囲です。
  • event.gamma: Y軸に沿った方向で、-90度から90度の範囲です。

ブラウザサポート

デバイスオリエンテーションAPIのブラウザサポート出典: Caniuse。 (大きなプレビュー)

バイスで動く要素

今回は、デバイスで回転できる CSS による 3D キューブを作成します!私が最初に CSS キューブを作成する際に使用した完全な手順は、David DeSandro に帰属し、彼の 3D トランスフォームの紹介 に記載されています。

Dave DeSandroによる[「Rotate cube forked]」をご覧ください。

デモでは生の完全な HTML を見ることができますが、後世のためにここに印刷しておきましょう。

<main>
  <div class="scene">
    <div class="cube">
      <div class="cube__face cube__face--front">1</div>
      <div class="cube__face cube__face--back">2</div>
      <div class="cube__face cube__face--right">3</div>
      <div class="cube__face cube__face--left">4</div>
      <div class="cube__face cube__face--top">5</div>
      <div class="cube__face cube__face--bottom">6</div>
    </div>
  </div>
  <h1>デバイスの向き API</h1>
  <p>
    アルファ:<span class="currentAlpha"></span>
    <br />
    ベータ版:<span class="currentBeta"></span>
    <br />
    ガンマ: <span class="currentGamma"></span>
  </p>
</main>

ここでは説明を省略しますが、CSSコードは3Dキューブに必要なスタイルを提供し、CSS rotate() functionを使用してすべての軸で回転させることができます。

次に、JavaScript を使用して、ウィンドウの deviceorientation イベントを監視し、イベントの方向データにアクセスします。

const currentAlpha = document.querySelector(".currentAlpha");
const currentBeta = document.querySelector(".currentBeta");
const currentGamma = document.querySelector(".currentGamma");

window.addEventListener("deviceorientation", (event) => {
  currentAlpha.textContent = event.alpha;
  currentBeta.textContent = event.beta;
  currentGamma.textContent = event.gamma;
});

デスクトップデバイス上でデータがどのように変化するかを見るには、Chromeデベロッパーツールを開き、センサーパネルにアクセスして回転デバイスをエミュレートします。

キューブを回転させるには、デバイスの向きのデータに応じて、CSStransformプロパティを変更します。

const currentAlpha = document.querySelector(".currentAlpha");
const currentBeta = document.querySelector(".currentBeta");
const currentGamma = document.querySelector(".currentGamma");

const cube = document.querySelector(".cube");

window.addEventListener("deviceorientation", (event) => {
  currentAlpha.textContent = event.alpha;
  currentBeta.textContent = event.beta;
  currentGamma.textContent = event.gamma;

  cube.style.transform = `rotateX(${event.beta}deg) rotateY(${event.gamma}deg) rotateZ(${event.alpha}deg)`;
});

その結果は次の通りです。

3. Vibration API

次に、Vibration API について説明します。これは、当然のことながら、デバイスの振動機能にアクセスするためのものです。これは、プロセスが終了したときやメッセージを受信したときなど、アプリ内の通知でユーザーに通知する必要がある場合に便利です。とはいえ、この機能の使用は控えめにする必要があります。誰も、通知で携帯電話が爆発するような事態は望んでいないでしょう。

Vibration API が提供するメソッドは 1 つだけで、それで十分です。それは navigator.vibrate() です。

vibrate()navigator オブジェクトからグローバルに利用でき、バイブレーションの継続時間をミリ秒単位で指定する引数を取ります。 バイブレーションとポーズの回数を表す数値または数値の配列を指定できます。

navigator.vibrate(200); // 200ミリ秒間振動します
navigator.vibrate([200, 100, 200]); // 200ミリ秒間振動し、100ミリ秒間待ち、さらに200ミリ秒間振動します。

ブラウザサポート

VIBRATION API デモ

ユーザーがデバイスの振動時間をミリ秒単位で入力し、振動の開始と停止を行うボタンを配置する簡単なデモを作成してみましょう。まずはマークアップから始めます。

<main>
  <form>
    <label for="milliseconds-input">ミリ秒:</label>
    <input type="number" id="milliseconds-input" value="0" />
  </form>

  <button class="vibrate-button">振動</button>
  <button class="stop-vibrate-button">Stop</button>
</main>

クリックイベントリスナーを追加し、vibrate()メソッドを呼び出します。

const vibrateButton = document.querySelector(".vibrate-button");
const millisecondsInput = document.querySelector("#milliseconds-input");

vibrateButton.addEventListener("click", () => {
  navigator.vibrate(millisecondsInput.value);
});

振動を停止するには、現在の振動をゼロミリ秒の振動で上書きします。

const stopVibrateButton = document.querySelector(".stop-vibrate-button");

stopVibrateButton.addEventListener("click", () => {
  navigator.vibrate(0);
});

4. Contact Picker API

以前は、ネイティブアプリのみがデバイスの「連絡先」に接続することができました。しかし、今回ご紹介する最後のAPIは、Contact Picker APIです。

この API により、ウェブアプリからデバイスの連絡先リストにアクセスできるようになります。具体的には、navigator オブジェクトから利用可能な非同期メソッド contacts.select() を使用します。このメソッドには、次の 2 つの引数があります。

  • properties: 連絡先カードから取得したい情報(例: "name", "address", "email", "tel", "icon")を含む配列です。
  • options: これは、ユーザーが一度に 1 人または複数の連絡先を選択できるかどうか定義する multiple ブール値プロパティのみを含むオブジェクトです。

ブラウザサポート

残念ながら、現時点ではブラウザサポートは皆無に等しい状態です。Chrome AndroidSamsung Internet、そして私がこの記事を書いている時点ではAndroidのネイティブウェブブラウザのみに対応しています。

ユーザーの連絡先を選択

ユーザーの連絡先を選択してページに表示するデモをもう1つ作成します。HTMLから始めます。

<main>
  <button class="get-contacts">連絡先を取得</button>
  <p>連絡先:</p>
  <ul class="contact-list">
    <!-- 連絡先の一覧を挿入します -->
  </ul>
</main>

次に、JavaScriptで、まずDOMから要素を構築し、連絡先から取得したいプロパティを選択します。

const getContactsButton = document.querySelector(".get-contacts");
const contactList = document.querySelector(".contact-list");

const props = ["name", "tel", "icon"];
const options = {multiple: true};

これで、ユーザーが getContactsButton をクリックすると、非同期で連絡先が取得されます。

const getContacts = async () => {
  try {
    const contacts = await navigator.contacts.select(props, options);
  } catch (error) {
    console.error(error);
};

getContactsButton.addEventListener("click", getContacts);

DOM操作を使用することで、各コンタクトにリスト項目を追加し、contactList要素にアイコンを追加することができます。

const appendContacts = (contacts) => {
  contacts.forEach(({name, tel, icon}) => {
    const contactElement = document.createElement("li");

    contactElement.innerText = `${name}: ${tel}`;
    contactList.appendChild(contactElement);
  });
};

const getContacts = async () => {
  try {
    const contacts = await navigator.contacts.select(props, options);
    appendContacts(contacts);
  } catch (error) {
    console.error(error);
  
};

getContactsButton.addEventListener("click", getContacts);

画像を追加するには少しコツが必要です。画像を URL に変換し、リストの各項目に追加する必要があるからです。

const getIcon = (icon) => {
  icon.length > 0)の場合、
    const imageUrl = URL.createObjectURL(icon[0]);
    const imageElement = document.createElement("img");
    imageElement.src = imageUrl;

    imageElement を返します。
  }
};

const appendContacts = (contacts) => {
  contacts.forEach(({name, tel, icon}) => {
    const contactElement = document.createElement("li");

    contactElement.innerText = `${name}: ${tel}`;
    contactList.appendChild(contactElement);

    const imageElement = getIcon(icon);
    contactElement.appendChild(imageElement);
  });
};

const getContacts = async () => {
  try {
    const contacts = await navigator.contacts.select(props, options);
    appendContacts(contacts);
  } catch (error) {
    console.error(error);
  
};

getContactsButton.addEventListener("click", getContacts);

そして、その結果がこちらです。

注:Contact Picker APIは、コンテキストがセキュアな場合のみ機能します。つまり、ページが https:// または wss:// URL で提供されている場合です。

結論

以上、より便利で堅牢なPWAを構築する上で役立つと思われる4つのウェブAPIをご紹介しましたが、多くの方にとってはまだあまり知られていないものだと思います。もちろん、これはブラウザのサポートが統一されていないことが原因です。この記事が新しいAPIの存在を周知させ、今後のブラウザアップデートでそれらを目にする機会が増えるきっかけになれば幸いです。

興味深いと思いませんか? 端末や画面の向きをコントロールできるだけでなく、端末のハードウェア機能(バイブレーションなど)や他のアプリからの情報にアクセスできるレベルも、独自の UI で使用できます。

しかし、先ほども申し上げたように、認識不足がブラウザサポートの欠如を生むという無限ループのような状況があります。そのため、今回ご紹介した4つのAPIは非常に興味深いものですが、本番環境でそれらを使用する際には、状況によって成果に差が出ることは避けられません。最新のサポート情報については Caniuse を参照するか、WebAPI Check を使用してご自身のデバイスを確認してください。