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

アプリのドメイン

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

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

HIIT Time サンプルアプリ

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

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

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

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

アプリの構造

上記のように、このアプリはナビゲーション バー、タブバー、3 つのページで構成され、グリッド状に配置されています。ナビバーとタブバーは iframe として実装され、その間に <div> コンテナがあり、ページ用の iframe がさらに 3 つあります。そのうち 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 などの一元的な設定を繰り返し宣言する必要はありません。ここでは、テーマとダークモードのオプションも定義されます。 以下のリストは、さまざまなフォーム要素をグリッド上に配置する Preferences ページのルールを示したものです。

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 によってレビューされました。