プログレッシブ ウェブアプリを段階的に強化する

最新のブラウザ向けに構築し、2003 年と同様に段階的に機能を強化

2003 年 3 月、Nick FinckSteve Champeon はウェブデザインの世界を驚かせました コンセプトを プログレッシブ エンハンスメント ウェブページの主要なコンテンツを最初に読み込むことに重点を置いたウェブデザイン戦略です。 それから徐々に 技術的に厳密なレイヤからなるプレゼンテーションと機能を コンテンツの上に重ねています 一方、2003 年の段階的な拡張は、当時としては CSS 機能、目立たない JavaScript、さらには拡張可能なベクター グラフィックなどです。 2020 年以降の漸進的な機能強化では、 最新のブラウザ機能

<ph type="x-smartling-placeholder">
</ph> 漸進的な機能強化による将来を見据えたインクルーシブなウェブデザイン。フィンクとチャンピオンの元のプレゼンテーションのタイトル スライド。 <ph type="x-smartling-placeholder">
</ph> スライド: プログレッシブ エンハンスメントによる将来を見据えたインクルーシブなウェブデザイン。 (出典)。
をご覧ください。

最新の JavaScript

JavaScript と言えば、最新のコア ES 2015 JavaScript のブラウザサポート状況です。 お話しします 新しい標準には、Promise、モジュール、クラス、テンプレート リテラル、アロー関数、letconst が含まれます。 デフォルト パラメータ、ジェネレータ、分離代入、休憩と拡散、Map/SetWeakMap/WeakSet など。 すべてサポート対象です

<ph type="x-smartling-placeholder">
</ph> 主なブラウザでの ES6 機能のサポート状況を示す CanIUse サポートの表。 <ph type="x-smartling-placeholder">
</ph> ECMAScript 2015(ES6)ブラウザ サポートの表。(出典)。
をご覧ください。

非同期関数、ES 2017 の機能、私の個人的なお気に入りの 1 つです。 使用できる 使用できます。 async キーワードと await キーワードを使用すると、非同期の Promise ベースの動作が可能になります。 よりクリーンなスタイルで記述できるようになるため、Promise チェーンを明示的に構成する必要がなくなります。

<ph type="x-smartling-placeholder">
</ph> すべての主要なブラウザでの非同期関数のサポートを示す CanIUse サポートの表。 <ph type="x-smartling-placeholder">
</ph> 非同期関数ブラウザのサポートの表。(出典)。
をご覧ください。

さらに、2020 年に新たに追加された言語 オプションのチェーンnull のような結合 サポートにすぐにたどり着けるようになりました。以下にコードサンプルを示します。 JavaScript の主要な機能として、芝生の緑がさほど広くない 今日です。

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
<ph type="x-smartling-placeholder">
</ph> Windows XP の象徴的な緑の草の背景画像。
JavaScript のコア機能については草が緑色になっています。 (Microsoft 製品のスクリーンショット、 権限が必要です)。

サンプルアプリ: Fugu Greetings

この記事では、 Fugu 挨拶GitHub)。 このアプリは、ウェブにすべてを費やす取り組み、Project Fugu 🐡? へのヒントとなっています Android/iOS/デスクトップ アプリケーションの機能を最大限に活用できます。 プロジェクトについて詳しくは、 ランディング ページ

Fugu Greetings は お絵かき用のお絵かきアプリです 共有できます具体例は PWA の基本コンセプトです。 信頼性が高く、完全にオフラインが有効になっているため、 使用できるわけではありません。また、インストール可能で、 オペレーティング システムとシームレスに統合し、 スタンドアロン アプリケーションとして利用できます。

<ph type="x-smartling-placeholder">
</ph> PWA コミュニティのロゴに似た図形描画が表示されている Fugu Greetings PWA。 <ph type="x-smartling-placeholder">
</ph> Fugu Greetings サンプルアプリ。

プログレッシブ エンハンスメント

では、プログレッシブ エンハンスメントについて説明します。 MDN Web ドキュメントの用語集では定義 次のようなコンセプトです。

プログレッシブ エンハンスメントは、 できる限り多くのユーザーに重要なコンテンツと機能を提供し、 可能な限り最良のエクスペリエンスを 必要なすべてのコードを実行できるブラウザです。

機能検出 ブラウザが最新の機能に対応できるかどうかを判断するために使用されます。 ポリフィルは は、JavaScript で不足している機能を追加するためによく使用されます。

[…]

プログレッシブ エンハンスメントは、ウェブ デベロッパーが 優れたウェブサイトを開発し、ウェブサイトを機能させながら、 複数の不明なユーザー エージェントに対応できます。 グレースフル デグラデーション 関連性はあるものの同じものではなく、逆の方向に進んでいると見なされることもよくあります プログレッシブ エンハンスメントに発展します。 実際には、どちらのアプローチも有効であり、多くの場合、互いに補完し合うことができます。

MDN のコントリビューター

グリーティング カードを最初から作成することは、とても面倒な作業です。 では、ユーザーがイメージをインポートして、そこから開始できる機能がないのではないでしょうか。 従来のアプローチでは、 <input type=file> これを行います まず、要素を作成して type'file' に設定し、MIME タイプを accept プロパティに追加します。 次にプログラムによって変更をリッスンします 画像を選択すると、キャンバスに直接インポートされます。

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

import 機能があるのに export 機能も用意するのが一般的である メッセージをローカルに保存できるようにします ファイルを保存する従来の方法は、アンカーリンクを作成することです。 download を使用 属性で示し、href として blob URL を指定します。 また プログラマティックにダウンロードがトリガーされます。 メモリリークを防ぐために、blob オブジェクトの URL を必ず取り消すようにしてください。

const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

でもちょっと待って。「ダウンロード」していないのがグリーティングカード、 「保存済み」できます。 「保存」というメッセージがダイアログでファイルの配置先を選択できます。 ユーザーの操作なしでブラウザがグリーティング カードを直接ダウンロードした ダウンロードフォルダにありますあまり良くありません。

もっと良い方法があったら? ローカル ファイルを開いて編集し、変更を保存するとしたらどうでしょうか。 新しいファイルに戻すか 最初に開いた元のファイルに戻すかです あることがわかりました。File System Access API ファイルを開いて作成したり、 変更して保存することもできます。

API を機能検出するにはどうすればよいでしょうか。 File System Access API は、新しいメソッド window.chooseFileSystemEntries() を公開します。 そのため、このメソッドが使用可能かどうかに応じて、異なるインポート モジュールとエクスポート モジュールを条件付きで読み込む必要があります。その方法については下記をご覧ください。

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

File System Access API の詳細に入る前に、 段階的な拡張のパターンを簡単に紹介します File System Access API を現在サポートしていないブラウザでは、以前のスクリプトを読み込みます。 Firefox と Safari のネットワーク タブを以下に示します。

<ph type="x-smartling-placeholder">
</ph> 以前のファイルが読み込まれていることを示す Safari ウェブ インスペクタ。 <ph type="x-smartling-placeholder">
</ph> Safari Web Inspector の [ネットワーク] タブ
で確認できます。
<ph type="x-smartling-placeholder">
</ph> 以前のファイルが読み込まれることを示す Firefox デベロッパー ツール。 <ph type="x-smartling-placeholder">
</ph> Firefox デベロッパー ツールの [ネットワーク] タブ。

ただし、Chrome(API をサポートするブラウザ)では、新しいスクリプトのみが読み込まれます。 これは、 動的 import(): すべての最新ブラウザ サポートをご覧ください。 先ほども言いましたが、最近は草がかなり緑色です。

<ph type="x-smartling-placeholder">
</ph> 最新のファイルが読み込まれている Chrome DevTools。 <ph type="x-smartling-placeholder">
</ph> Chrome DevTools の [ネットワーク] タブ。

File System Access API

問題に対処できたので、次は File System Access API に基づく実際の実装を見ていきます。 画像をインポートするには、window.chooseFileSystemEntries() を呼び出します。 そして、画像ファイルが必要なことを示す accepts プロパティを渡します。 ファイル拡張子と MIME タイプの両方がサポートされています。 これにより、ファイル ハンドルが返され、getFile() を呼び出して実際のファイルを取得できます。

const importImage = async () => {
  try {
    const handle = await window.chooseFileSystemEntries({
      accepts: [
        {
          description: 'Image files',
          mimeTypes: ['image/*'],
          extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
        },
      ],
    });
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

イメージのエクスポート方法はほぼ同じですが、今回は 'save-file' の型パラメータを chooseFileSystemEntries() メソッドに渡す必要があります。 ファイル保存ダイアログが表示されます。 ファイルを開いている場合、'open-file' がデフォルトであるため、この操作は必要ありません。 以前と同様に accepts パラメータを設定しますが、今回は PNG 画像のみに限定しています。 ファイルハンドルは返されますが、 今回は、createWritable() を呼び出して書き込み可能なストリームを作成します。 次に、グリーティング カードの画像である blob をファイルに書き込みます。 最後に、書き込み可能なストリームを閉じます。

ディスクの空き容量不足や 書き込みまたは読み取りエラーが発生したり、ユーザーがファイル ダイアログをキャンセルしたりする可能性があります。 そのため、呼び出しを常に try...catch ステートメントでラップしています。

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

File System Access API によるプログレッシブ エンハンスメントの使用 前と同じようにファイルを開くことができます。 インポートしたファイルはキャンバスに直接描画されます。 編集を加えたら、最後に実際の保存ダイアログ ボックスで保存できます。 ファイルの名前と保存場所を選択できます これで、ファイルを永続的に保持できるようになりました。

<ph type="x-smartling-placeholder">
</ph> ファイルを開くダイアログが表示された Fugu Greetings アプリ <ph type="x-smartling-placeholder">
</ph> ファイルを開くダイアログ。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> Fugu Greetings アプリにイメージがインポートされるようになりました。 <ph type="x-smartling-placeholder">
</ph> インポートされたイメージ。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> 画像を変更した Fugu Greetings アプリ <ph type="x-smartling-placeholder">
</ph> 変更した画像を新しいファイルに保存します。

Web Share API と Web Share Target API

永遠に保管する以外に、グリーティング カードを共有したいと思うかもしれません。 これは Web Share APIWeb Share Target API を使用すると、 モバイル、および最近ではデスクトップ オペレーティング システムに、組み込みの共有機能が導入されている メカニズムです。 たとえば、以下は macOS 上のデスクトップ Safari の共有シートが、 私のブログ [記事を共有] ボタンをクリックすると、記事へのリンクを友だちと共有できます。 macOS のメッセージ アプリなどです。

<ph type="x-smartling-placeholder">
</ph> 記事の [共有] ボタンから、macOS でパソコン版の Safari の共有シートがトリガーされる <ph type="x-smartling-placeholder">
</ph> macOS のデスクトップ Safari での Web Share API。

これを行うためのコードは非常に簡単です。navigator.share() に電話をかけ、 オブジェクトでオプションの titletexturl を渡します。 画像を添付したい場合はどうすればよいでしょうかこれは、Web Share API のレベル 1 ではまだサポートされていません。 幸いなことに、ウェブ共有レベル 2 にファイル共有機能が追加されています。

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

Fugu Greeting card アプリケーションでこれを行う方法をご紹介します。 まず、1 つの blob で構成される files 配列を持つ data オブジェクトを準備する必要があります。 titletext の 2 つです。次に、ベスト プラクティスとして、新しい navigator.canShare() メソッドを使用します。 その名前が示すとおりです。 共有しようとしている data オブジェクトを、技術的にはブラウザが共有できるかどうかを示します。 navigator.canShare() からデータを共有可能と表示されたら、すぐに使用できる 以前と同様に navigator.share() を呼び出します。 すべてが失敗する可能性があるため、ここでも try...catch ブロックを使用します。

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

前と同様に、プログレッシブ エンハンスメントを使用します。 'share''canShare' の両方が navigator オブジェクトに存在する場合は、次の手順に進みます。 動的な import() を使用して share.mjs を読み込む。 モバイル版 Safari など、2 つの条件のいずれかしか満たさないブラウザでは読み込まれない 説明します。

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

Fugu Greetings では、Android 版 Chrome などの対応ブラウザで [共有] ボタンをタップすると、 組み込みの共有シートが開きます。 たとえば Gmail を選択すると、メール作成ウィジェットが 画像を添付しました。

<ph type="x-smartling-placeholder">
</ph> イメージを共有するさまざまなアプリを示す OS レベルの共有シート。 <ph type="x-smartling-placeholder">
</ph> ファイルの共有先アプリを選択します。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> 画像が添付された Gmail のメール作成ウィジェット。 <ph type="x-smartling-placeholder">
</ph> ファイルは Gmail の作成ツールで新しいメールに添付されます。

Contact Picker API

次に、連絡先、つまりデバイスのアドレス帳についてお話しします または連絡先管理ツールアプリを使用していました グリーティング カードを書くときに、 おすすめします。 たとえば、友人のサーゲイさんは、自分の名前をキリル文字で表記することを好みます。私は ドイツ語の QWERTZ キーボードを使用しているため、名前の入力方法がわかりません。 この問題は Contact Picker API で解決できます。 スマートフォンの連絡帳アプリに友だちが登録されているので、 ウェブの連絡先をタップするだけです

まず、アクセスするプロパティのリストを指定する必要があります。 ここでは名前のみを指定します。 他のユースケースでは電話番号、メールアドレス、アバターが アイコン、住所。 次に、options オブジェクトを構成し、multipletrue に設定して、さらに選択できるようにします。 重複していないからです 最後に、navigator.contacts.select() を呼び出します。これにより、目的のプロパティが返されます。 自動的に有効になります。

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

ここまでで、次のパターンを学んできました。 API が実際にサポートされている場合にのみ、ファイルを読み込みます。

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

Fugu Greeting で [連絡先] ボタンをタップして、友だち候補を 2 つ選択します。 сергей оивайлови 日後 Брин劳伦·爱德摩里"·佩奇、 ご覧のとおり、 連絡先選択ツールに表示されるのは メールアドレスなどの電話番号などの情報は 共有できません その後、名前がグリーティング カードに描画されます。

<ph type="x-smartling-placeholder">
</ph> アドレス帳の 2 件の連絡先の名前が表示された連絡先選択ツール。 <ph type="x-smartling-placeholder">
</ph> アドレス帳の連絡先選択ツールで 2 つの名前を選択する。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> グリーティング カードに描画された、以前に選択した 2 つの連絡先の名前。 <ph type="x-smartling-placeholder">
</ph> 2 人の名前がグリーティング カードに描画されます。

Asynchronous Clipboard API

次はコピーと貼り付けです ソフトウェア開発者が好む操作の 1 つがコピー&ペーストです。 私はグリーティング カードの作成者として、ときどき同じことを行いたいことがあります。 作成中のグリーティングカードに画像を貼り付けるか メッセージ カードをコピーして編集を続けるか、 別の場所に移動します Async Clipboard API テキストと画像の両方をサポートしています コピー&ペーストのサポートを Fugu に追加した方法を見ていきましょう。 応答メッセージ アプリ。

システムのクリップボードに何かコピーするには、それに書き込む必要があります。 navigator.clipboard.write() メソッドは、クリップボードのアイテムの配列を パラメータを指定します。 クリップボードの各アイテムは基本的に、blob を値として持つオブジェクトであり、blob の型は 使用します。

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

貼り付けるには、 navigator.clipboard.read()。 これは、クリップボードに複数のクリップボード アイテムがある可能性があるためです。 トレーニングされます。 クリップボードの各アイテムには、利用可能なコンテンツの MIME タイプを示す types フィールドがあります。 説明します。 クリップボード アイテムの getType() メソッドを呼び出して、 以前に取得した MIME タイプ。

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

今のところはほぼ言うまでもありません。対応ブラウザでのみ対応しています。

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

では、これは実際にどのように機能するのでしょうか。macOS プレビュー アプリで画像を開いていて、 クリップボードにコピーします。 [Paste] をクリックすると、Fugu Greetings アプリから クリップボードのテキストや画像を参照することをアプリに許可するかどうかを指定します。

<ph type="x-smartling-placeholder">
</ph> クリップボードの権限プロンプトを表示する Fugu Greetings アプリ <ph type="x-smartling-placeholder">
</ph> クリップボードの権限プロンプト。

最後に、許可が得られたら、画像がアプリケーションに貼り付けます。 その逆も同様です。 グリーティング カードをクリップボードにコピーします。 [プレビュー] を開いて [ファイル]、[クリップボードから新規作成] の順にクリックします。 グリーティング カードは無題の新しい画像に貼り付けられます。

<ph type="x-smartling-placeholder">
</ph> 無題の画像を貼り付けたばかりの macOS Preview アプリ。 <ph type="x-smartling-placeholder">
</ph> macOS プレビュー アプリに貼り付けられた画像。
をご覧ください。

Badging API

便利な API として、Badging API もあります。 Fugu Greetings にはインストール可能な アプリアイコンがあります アプリドックやホーム画面に配置できます。 Fugu Greetings で API を(誤用)使用すると、楽しく簡単に API のデモを行うことができます。 カウンタとして利用できます pointerdown イベントが発生するたびにペンのストローク カウンタをインクリメントするイベント リスナーを追加しました。 更新したアイコンバッジを設定します キャンバスがクリアされると、カウンタがリセットされ、バッジが削除されます。

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

この機能は段階的に強化されているため、読み込みロジックは通常と同じです。

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

この例では、ペンのストロークを 1 本使用して 1 から 7 までの数字を引いています。 割った数値です アイコンのバッジカウンターが 7 になっています。

<ph type="x-smartling-placeholder">
</ph> グリーティング カードに描画された 1 ~ 7 の数字。それぞれがペンの 1 ストロークで描かれています。 <ph type="x-smartling-placeholder">
</ph> 7 本のペンのストロークで 1 から 7 までの数字を描画する。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> Fugu Greetings アプリのバッジアイコンに数字の 7 が表示されています。 <ph type="x-smartling-placeholder">
</ph> アプリアイコン バッジの形をしたペンのストローク カウンタ。

Periodic Background Sync API

毎日新しいことを始めるには、 Fugu Greetings アプリの優れた点は、毎朝インスピレーションを与えられることです。 グリーティング カードを開始しましょう。 アプリが Periodic Background Sync API を使用する 必要があります。

最初のステップは、Service Worker の登録に定期的な同期イベントを登録することです。 'image-of-the-day' という同期タグをリッスンします。 最小間隔が 1 日の場合 これにより、ユーザーは 24 時間ごとに新しい背景画像を取得できるようになります。

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

2 つ目のステップとして、Service Worker での periodicsync イベントのリッスンを行います。 イベントタグが 'image-of-the-day'(以前に登録されたもの)の場合、 その日の画像が getImageOfTheDay() 関数によって取得されます。 その結果がすべてのクライアントに反映されるため、クライアントはキャンバスと キャッシュです。

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

繰り返しになりますが、これは段階的な機能強化です。コードが読み込まれるのは API はブラウザでサポートされています。 これは、クライアント コードと Service Worker コードの両方に適用されます。 非対応ブラウザでは、どちらも読み込まれません。 動的な import() ではなく、Service Worker でその手順を確認します。 (Service Worker のコンテキストでは、 まだ)、 従来の importScripts()

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

Fugu Greetings で [Wallpaper] ボタンを押すと、その日のグリーティング カードの画像が表示されます。 Periodic Background Sync API によって毎日更新されます。

<ph type="x-smartling-placeholder">
</ph> 今日の新しいグリーティング カードの画像が表示されている Fugu Greetings アプリ。 <ph type="x-smartling-placeholder">
</ph> [壁紙] ボタンを押すと、その日の画像が表示されます。

通知トリガー API

インスピレーションがたくさんあるときでも、開始したあいさつを終えるためにちょっとしたきっかけが必要です 。 この機能は、Notification Triggers API によって有効になります。 ユーザーとして、グリーティング カードを完成させるまでの時間を入力できます。 指定した時間になると、グリーティング カードが待っていることを知らせる通知が届きます。

目標時刻の入力を求められたら アプリケーションが showTrigger を使用して通知をスケジュール設定する。 以前に選択した対象日を含む TimestampTrigger を指定できます。 リマインダー通知はローカルでトリガーされるため、ネットワークやサーバー側は必要ありません。

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

これまで説明してきた他のすべてと同様 これは漸進的な機能強化であり コードは条件付きでのみ読み込まれます

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

Fugu Greetings の [Reminder] チェックボックスをオンにすると、 メッセージに返信できます。

<ph type="x-smartling-placeholder">
</ph> Fugu Greetings アプリに、グリーティング カードの作成のリマインダーを受け取るタイミングを尋ねるプロンプトが表示されています。 <ph type="x-smartling-placeholder">
</ph> グリーティング カードを完成させるようリマインドするローカル通知をスケジュール設定する。

Fugu Greetings でスケジュール設定された通知がトリガーされると、 他の通知と同じように表示されますが ネットワーク接続も必要ありませんでした。

<ph type="x-smartling-placeholder">
</ph> Fugu Greetings からのトリガーされた通知を表示している macOS の通知センター。 <ph type="x-smartling-placeholder">
</ph> トリガーされた通知が macOS 通知センターに表示されます。

Wake Lock API

Wake Lock API も追加したいです。 何か思いつくまで画面をじっと見つめておきたい場合もあるでしょう あなたにキスをする。 最悪の場合、画面がオフになることがあります。 Wake Lock API を使用すると、このような事態を回避できます。

最初のステップは、navigator.wakelock.request method() で wake lock を取得することです。 画面の wake lock を取得するために文字列 'screen' を渡します。 次に、ウェイクロックが解除されたことを通知するイベント リスナーを追加します。 これは、タブの公開設定が変更された場合などに発生することがあります。 その場合は、タブが再び表示されたときに wake lock を再度取得できます。

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

はい。これは段階的な機能強化です。ブラウザが開いたときに読み込むだけで済みます サポートしています。

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

Fugu Greetings には [Insomnia] チェックボックスがあり、オンにすると 画面がスリープ解除されます。

<ph type="x-smartling-placeholder">
</ph> 不眠のチェックボックスがオンになっていると、画面がスリープ解除されたままになります。 <ph type="x-smartling-placeholder">
</ph> [Insomnia] チェックボックスをオンにすると、アプリが起動したままになります。

Idle Detection API

何時間も画面を見ていたとしても、 これだけでは役に立たず、グリーティング カードの使い方がまったく思いつかなくなります。 Idle Detection API を使用すると、アプリでユーザーのアイドル時間を検出できます。 ユーザーが長時間アイドル状態になると、アプリは初期状態にリセットされます。 キャンバスをクリアします この API は現在、 通知権限 本番環境のユースケースの多くは通知に関連しているため、 たとえば、ユーザーが現在使用しているデバイスにのみ通知を送信できます。

通知権限が付与されていることを確認したら、 アイドル状態の検出機能。 アイドル状態の変更をリッスンするイベント リスナーを登録します。このリスナーには、ユーザーや、 作成します。 ユーザーはアクティブでもアイドル状態でも 画面のロックまたはロック解除ができます。 ユーザーがアイドル状態になると、キャンバスはクリアされます。 アイドル検出器のしきい値は 60 秒です。

const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
  const userState = idleDetector.userState;
  const screenState = idleDetector.screenState;
  console.log(`Idle change: ${userState}, ${screenState}.`);
  if (userState === 'idle') {
    clearCanvas();
  }
});

await idleDetector.start({
  threshold: 60000,
  signal,
});

いつものように、このコードはブラウザでサポートされている場合にのみ読み込みます。

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

Fugu Greetings アプリでは、[Ephemeral] チェックボックスをオフにするとキャンバスがクリアされる ユーザーが長時間アイドル状態になっている。

<ph type="x-smartling-placeholder">
</ph> ユーザーが長時間アイドル状態になった後、キャンバスがクリアされた Fugu Greetings アプリ。 <ph type="x-smartling-placeholder">
</ph> [エフェメラル] チェックボックスがオンになっている場合、ユーザーが長時間アイドル状態になっていると、キャンバスはクリアされます。

結びの言葉

すごい、なんて乗り物だ。1 つのサンプルアプリに膨大な API があります。 ユーザーにダウンロードコストを 支払わせることはありません ブラウザでサポートされていない機能を表示する場合 プログレッシブ エンハンスメントを使用することで、関連するコードのみが読み込まれるようにしています。 また、HTTP/2 ではリクエストが安価であるため、このパターンは多くのユースケースでうまく機能するはずです。 アプリケーション、 非常に大規模なアプリにはバンドラを検討することをおすすめします。

<ph type="x-smartling-placeholder">
</ph> 現在のブラウザでサポートされているコードを含むファイルのリクエストのみが表示されている Chrome DevTools Network パネル。 <ph type="x-smartling-placeholder">
</ph> 現在のブラウザでサポートされているコードを含むファイルのリクエストのみを示す Chrome DevTools の [ネットワーク] タブ。

プラットフォームによっては対応していない機能があるため、アプリはブラウザによって若干異なる場合があります。 コア機能は常にそこにあり、特定のブラウザの機能に応じて段階的に強化されています。 これらの機能は、同じブラウザでも変更される可能性がありますが、 アプリがインストール済みのアプリとして実行されているか、ブラウザタブで実行されているかによって異なります。

<ph type="x-smartling-placeholder">
</ph> Android Chrome で動作する Fugu Greetings。利用可能な機能の多くが表示されています。 <ph type="x-smartling-placeholder">
</ph> Android Chrome で動作する Fugu Greetings
で確認できます。
<ph type="x-smartling-placeholder">
</ph> パソコンの Safari で Fugu 応答メッセージが実行され、利用可能な機能が少なくなります。 <ph type="x-smartling-placeholder">
</ph> パソコンの Safari で動作する Fugu Greetings
で確認できます。
<ph type="x-smartling-placeholder">
</ph> パソコンの Chrome で動作している Fugu Greetings。利用可能な機能が多数表示されています。 <ph type="x-smartling-placeholder">
</ph> パソコンの Chrome で動作する Fugu Greetings

Fugu Greetings アプリに関心がある場合は、 GitHub でフォークしてください。

<ph type="x-smartling-placeholder">
</ph> GitHub の Fugu Greetings リポジトリ。 <ph type="x-smartling-placeholder">
</ph> GitHub の Fugu Greetings アプリ。

Chromium チームは、高度な Fugu API のリリースによって芝をグリーンにするように尽力しています。 アプリの開発に段階的な機能強化を適用すると まず 誰もが良質で強固なベースライン 経験を得られるように より多くのウェブ プラットフォーム API をサポートするブラウザを使用するほど、ユーザー エクスペリエンスはさらに向上します。 アプリの先進的な機能向上をぜひご検討ください。

謝辞

クリスチャン・リーベルに感謝します。 Fugu Greetings に寄与した Hemanth HM。 この記事は Joe Medley によってレビューされ、 Kayce BasquesJake Archibald が状況を知るのを手伝ってくれました Service Worker のコンテキストで動的な import() を使用します。