ミニアプリのプログラミングの原則をサンプル プロジェクトに適用する

アプリのドメイン

ウェブアプリに適用されるミニアプリによるプログラミングを示すには、小さいながらも十分なアプリのアイデアが必要でした。高強度インターバル トレーニング(HIIT)は、短時間の激しい嫌気性運動を交互に行う心臓血管の運動戦略で、回復時間はそれほど大きくありません。多くの HIIT トレーニングでは HIIT タイマーを使用します。たとえば、YouTube チャンネルの The Body Coach TV30 分間のオンライン セッションなどです。

緑色の高強度タイマー付きの HIIT トレーニング オンライン セッション。
有効期間。
赤色の低強度タイマーが表示された HIIT トレーニングのオンライン セッション。
休息時間。

HIIT Time サンプルアプリ

この章では、このような HIIT タイマー アプリケーションの基本的な例を作成しました。このアプリケーションの名前は「HIIT Time」です。ユーザーは、さまざまなタイマーを定義して管理できます。これらのタイマーは常に高強度と低強度のインターバルで構成され、トレーニング セッションに 1 つを選択できます。ナビゲーション バー、タブバー、3 つのページを備えたレスポンシブ アプリです。

  • ワークアウト: ワークアウト中のアクティブなページ。ユーザーはタイマーの 1 つを選択し、セット数、アクティブ期間、休憩期間の 3 つの進行状況リングを表示できます。
  • タイマー: 既存のタイマーを管理し、ユーザーが新しいタイマーを作成できるようにします。
  • 設定: 効果音と音声出力の切り替え、言語とテーマの選択ができます。

次のスクリーンショットは、アプリケーションの概要を示しています。

縦表示の HIIT Time サンプルアプリ。
縦向きモードの HIIT 時間の [ワークアウト] タブ。
横表示の HIIT Time サンプルアプリ。
横表示の HIIT 時間の [ワークアウト] タブ。
タイマーの管理を示す HIIT Time の例アプリ。
HIIT 時間タイマーの管理。

アプリの構造

上記のように、このアプリはナビゲーション バー、タブバー、3 つのページで構成され、グリッド状に配置されています。ナビゲーションバーとタブバーは iframe として実現され、その間に <div> コンテナがあり、ページ用にさらに 3 つの iframe があります。そのうちの 1 つは常に表示され、タブバーでのアクティブな選択に依存します。about:blank を指す最後の iframe は、動的に作成されたアプリ内ページに使用されます。これは、既存のタイマーの変更や新しいタイマーの作成に必要です。私はこのパターンをマルチページ シングルページ アプリ(MPSPA)と呼んでいます。

アプリの HTML 構造を示す Chrome DevTools ビュー。6 つの iframe(ナビゲーション バーに 1 つ、タブバーに 1 つ、アプリの各ページにグループ化された 3 つの iframe と動的ページ用の最後のプレースホルダ iframe がある)が表示されています。
アプリは 6 つの iframe で構成されています。

コンポーネントベースの lit-html マークアップ

各ページの構造は、実行時に動的に評価される lit-html スキャフォールドとして実現されます。lit-html の背景には、効率的で表現力が高く、拡張可能な JavaScript 用の HTML テンプレート ライブラリです。HTML ファイルで直接使用することで、メンタル プログラミング モデルは直接出力指向になります。プログラマーは、最終出力がどのようなものになるかのテンプレートを作成します。その後、lit-html がデータに基づいてそのギャップを動的に埋め、イベント リスナーを接続します。このアプリは、Shoelace<sl-progress-ring> などのサードパーティ製カスタム要素や、<human-duration> という独自実装のカスタム要素を使用しています。カスタム要素には宣言型 API(進行状況リングの percentage 属性など)があるため、下記のリストに示すように、lit-html とうまく連携します。

<div>
  <button class="start" @click="${eventHandlers.start}" type="button">
    ${strings.START}
  </button>
  <button class="pause" @click="${eventHandlers.pause}" type="button">
    ${strings.PAUSE}
  </button>
  <button class="reset" @click="${eventHandlers.reset}" type="button">
    ${strings.RESET}
  </button>
</div>

<div class="progress-rings">
  <sl-progress-ring
    class="sets"
    percentage="${Math.floor(data.sets/data.activeTimer.sets*100)}"
  >
    <div class="progress-ring-caption">
      <span>${strings.SETS}</span>
      <span>${data.sets}</span>
    </div>
  </sl-progress-ring>
</div>
3 つのボタンと進行状況グラフ。
上記のマークアップに対応するページのレンダリングされたセクション。

プログラミング モデル

各ページには、イベント ハンドラの実装と各ページのデータを提供することで、lit-html マークアップに生命を吹き込む対応する Page クラスがあります。このクラスは、onShow()onHide()onLoad()onUnload() などのライフサイクル メソッドもサポートしています。ページは、必要に応じて永続化されたページごとの状態とグローバル状態を共有するデータストアにアクセスできます。すべての文字列が集中管理されるため、国際化が組み込まれています。アプリは iframe の表示の切り替えだけを行い、動的に作成されたページではプレースホルダ iframe の src 属性を変更するため、ルーティングは基本的にブラウザによって処理されます。以下の例は、動的に作成されたページを閉じるコードを示しています。

import Page from '../page.js';

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
iframe として実装されたアプリ内ページ。
iframe から iframe へのナビゲーションが行われます。

スタイル設定

ページのスタイル設定は、独自のスコープ CSS ファイルでページごとに行われます。つまり、他のページと競合することがないため、通常は要素名で要素を直接指定できます。グローバル スタイルは各ページに追加されるため、font-familybox-sizing などの一元的な設定を繰り返し宣言する必要はありません。ここでは、テーマとダークモードのオプションも定義されます。 以下のリストは、さまざまなフォーム要素をグリッドに配置する [設定] ページのルールを示しています。

main {
  max-width: 600px;
}

form {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: 0.5rem;
  margin-block-end: 1rem;
}

label {
  text-align: end;
  grid-column: 1 / 2;
}

input,
select {
  grid-column: 2 / 3;
}
グリッド レイアウトでフォームを表示する HIIT Time アプリの設定ページ。
ページごとに世界観が異なります。スタイル設定は要素名で直接行われます。

画面の wake lock

ワークアウト中は画面がオフにならないようにする必要があります。これをサポートしているブラウザでは、HIIT Time は画面ウェイクロックを使用してこれを実現します。以下のスニペットは、その方法を示しています。

if ('wakeLock' in navigator) {
  const requestWakeLock = async () => {
    try {
      page.shared.wakeLock = await navigator.wakeLock.request('screen');
      page.shared.wakeLock.addEventListener('release', () => {
        // Nothing.
      });
    } catch (err) {
      console.error(`${err.name}, ${err.message}`);
    }
  };
  // Request a screen wake lock…
  await requestWakeLock();
  // …and re-request it when the page becomes visible.
  document.addEventListener('visibilitychange', async () => {
    if (
      page.shared.wakeLock !== null &&
      document.visibilityState === 'visible'
    ) {
      await requestWakeLock();
    }
  });
}

アプリケーションのテスト

HIIT Time アプリケーションは GitHub で入手できます。デモは、新しいウィンドウで再生できます。また、モバイル デバイスをシミュレートする以下の iframe 埋め込みで直接再生することもできます。

謝辞

この記事は、Joe MedleyKayce BasquesMilica MihajlijaAlan Kent、Keith Gu が確認しました。