Interaction to Next Paint(INP)を理解する

Interaction to Next Paint(INP)について

この Codelab について

subject最終更新: 1月 9, 2025
account_circle作成者: Michal Mocny, Brendan Kenny

1. はじめに

Interaction to Next Paint(INP)について学べるインタラクティブなデモと Codelab です。

メインスレッドでのインタラクションを描写した図。ユーザーがタスクの実行をブロックしながら入力をします。これらのタスクが完了するまで入力が遅延し、その後 pointerup、mouseup、click のイベント リスナーが実行され、次のフレームが表示されるまでレンダリングとペイントの作業が開始されます。

前提条件

学習内容

  • ユーザー操作と、その操作の処理がページの応答性にどのように影響するか。
  • 遅延を減らしてスムーズなユーザー エクスペリエンスを実現する方法。

必要なもの

  • GitHub からコードのクローンを作成し、npm コマンドを実行できるパソコン。
  • テキスト エディタ。
  • すべてのインタラクション測定が機能する Chrome の最新バージョン。

2. セットアップする

コードを取得して実行する

コードは web-vitals-codelabs リポジトリにあります。

  1. ターミナルでリポジトリのクローンを作成します。git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. クローン作成したディレクトリに移動します。cd web-vitals-codelabs/understanding-inp
  3. 依存関係をインストールします。npm ci
  4. ウェブサーバーを起動します。npm run start
  5. ブラウザで http://localhost:5173/understanding-inp/ にアクセスします。

アプリの概要

ページの上部には、スコア カウンタと [増やす] ボタンがあります。リアクティビティと応答性の古典的なデモです。

この Codelab のデモアプリのスクリーンショット

ボタンの下には、次の 4 つの測定値が表示されます。

  • INP: 現在の INP スコア。通常は最も遅延の大きいインタラクションです。
  • Interaction: 直近のインタラクションのスコア。
  • FPS: ページのメインスレッドのフレーム / 秒。
  • タイマー: ジャンクを視覚化するのに役立つ実行中のタイマー アニメーション。

FPS とタイマーのエントリは、インタラクションの測定にはまったく必要ありません。これらは、レスポンシブ性を視覚化しやすくするために追加されたものです。

試してみる

[Increment] ボタンをクリックして、スコアが増加するのを確認します。INPインタラクションの値は、増分ごとに変化しますか?

INP は、ユーザーが操作してから、レンダリングされた更新が実際にユーザーに表示されるまでの時間を測定します。

3. Chrome DevTools でインタラクションを測定する

その他のツール > デベロッパー ツール メニューから DevTools を開くか、ページを右クリックして [検証] を選択するか、キーボード ショートカットを使用します。

[パフォーマンス] パネルに切り替えます。このパネルを使用してインタラクションを測定します。

アプリとともに表示された DevTools の [Performance] パネルのスクリーンショット

次に、[パフォーマンス] パネルでインタラクションをキャプチャします。

  1. 録画ボタンを押します。
  2. ページを操作します([Increment] ボタンを押します)。
  3. 録画を停止します。

結果のタイムラインに [Interactions] トラックが表示されます。左側の三角形をクリックして展開します。

DevTools の [パフォーマンス] パネルを使用してインタラクションを記録するアニメーション デモ

2 つのインタラクションが表示されます。スクロールするか W キーを押して、2 つ目の画像を拡大します。

DevTools の [パフォーマンス] パネルのスクリーンショット。パネル内のインタラクションにカーソルが合わされ、インタラクションの短いタイミングがツールチップに表示されている

インタラクションにカーソルを合わせると、インタラクションが高速で、処理時間がゼロで、入力遅延表示遅延が最小限であることがわかります。正確な長さはマシンの速度によって異なります。

4. 長時間実行されるイベント リスナー

index.js ファイルを開き、イベント リスナー内の blockFor 関数のコメントを解除します。

コード全体を見る: click_block.html

button.addEventListener('click', () => {
  blockFor
(1000);
  score
.incrementAndUpdateUI();
});

ファイルを保存します。サーバーが変更を検知し、ページを更新します。

ページをもう一度操作してみてください。操作が明らかに遅くなります。

パフォーマンス トレース

パフォーマンス パネルで別の記録を作成して、その様子を確認します。

[パフォーマンス] パネルでの 1 秒間のインタラクション

以前は短いインタラクションだったものが、今では 1 秒かかるようになっています。

インタラクションにカーソルを合わせると、時間のほとんどが「処理時間」に費やされていることがわかります。これは、イベント リスナー コールバックの実行にかかった時間です。ブロッキング blockFor 呼び出しはイベント リスナー内に完全に含まれているため、時間がかかるのはこの部分です。

5. テスト: 処理期間

イベント リスナーの作業を再配置して、INP への影響を確認します。

UI を先に更新する

js 呼び出しの順序を入れ替えて、最初に UI を更新してからブロックするとどうなりますか?

コード全体: ui_first.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  blockFor
(1000);
});

以前に UI が表示されたことはありますか?順序は INP スコアに影響しますか?

トレースを取得してインタラクションを調べ、違いがないか確認してみてください。

リスナーを分離する

作業を別のイベント リスナーに移動するとどうなるでしょうか?1 つのイベント リスナーで UI を更新し、別のリスナーでページをブロックします。

コード全体: two_click.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('click', () => {
  blockFor
(1000);
});

[パフォーマンス] パネルではどのように表示されますか?

さまざまなイベントタイプ

ほとんどのインタラクションでは、ポインタ イベントやキーイベントから、ホバー、フォーカス/ブラー、beforechange や beforeinput などの合成イベントまで、さまざまな種類のイベントが発生します。

実際の多くのページには、さまざまなイベントのリスナーがあります。

イベント リスナーのイベントタイプを変更するとどうなりますか?たとえば、click イベント リスナーの 1 つを pointerup または mouseup に置き換えることはできますか?

コード全体を見る: diff_handlers.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('pointerup', () => {
  blockFor
(1000);
});

UI の更新なし

イベント リスナーから UI を更新する呼び出しを削除するとどうなりますか?

コード全体を見る: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});

6. 処理期間のテスト結果

パフォーマンス トレース: UI を最初に更新する

コード全体: ui_first.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  blockFor
(1000);
});

ボタンをクリックしたパフォーマンス パネルの記録を見ると、結果が変わっていないことがわかります。UI の更新はブロック コードの前にトリガーされましたが、ブラウザはイベント リスナーが完了するまで画面に描画された内容を更新しませんでした。つまり、インタラクションの完了には 1 秒強かかりました。

[パフォーマンス] パネルの 1 秒間のインタラクション

パフォーマンス トレース: リスナーを分離

コード全体: two_click.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('click', () => {
  blockFor
(1000);
});

機能的には違いはありません。インタラクションには 1 秒かかります。

クリック操作を拡大すると、click イベントの結果として 2 つの異なる関数が呼び出されていることがわかります。

予想どおり、UI の更新は非常に高速で実行されますが、2 番目の処理には 1 秒かかります。ただし、これらの効果を合計すると、エンドユーザーに対するインタラクションの遅延は同じになります。

この例の 1 秒間のインタラクションを拡大した図。最初の関数呼び出しが 1 ミリ秒未満で完了していることがわかる

パフォーマンス トレース: さまざまなイベントタイプ

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

button
.addEventListener('pointerup', () => {
  blockFor
(1000);
});

これらの結果は非常に似ています。インタラクションは 1 秒のままです。違いは、短い UI 更新のみの click リスナーが、ブロックする pointerup リスナーのに実行されるようになったことだけです。

この例の 1 秒間のインタラクションを拡大して表示した図。pointerup リスナーの後に、クリック イベント リスナーが 1 ミリ秒未満で完了していることがわかる

パフォーマンス トレース: UI の更新なし

コード全体を見る: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});
  • スコアは更新されませんが、ページは更新されます。
  • アニメーション、CSS 効果、デフォルトのウェブ コンポーネント アクション(フォーム入力)、テキスト入力、テキストのハイライト表示はすべて引き続き更新されます。

この場合、ボタンはクリックされるとアクティブな状態になり、すぐに戻ります。これにはブラウザによるペイントが必要になるため、INP が発生します。

イベント リスナーがメインスレッドを 1 秒間ブロックしてページが描画されないようにしたため、インタラクションには 1 秒かかります。

パフォーマンス パネルの記録を撮ると、以前の操作とほぼ同じ操作が表示されます。

[パフォーマンス] パネルの 1 秒間のインタラクション

重要なポイント

任意のイベント リスナーで実行される任意のコードは、インタラクションを遅延させます。

  • これには、さまざまなスクリプトから登録されたリスナーや、リスナーで実行されるフレームワークまたはライブラリ コード(コンポーネントのレンダリングをトリガーする状態の更新など)が含まれます。
  • 独自のコードだけでなく、すべてのサードパーティ スクリプトも対象となります。

よくある問題です。

最後に、コードがペイントをトリガーしないからといって、ペイントが遅いイベント リスナーの完了を待機しないとは限りません。

7. Experiment: input delay

イベント リスナーの外部で実行される長時間実行コードはどうなりますか?次に例を示します。

  • 読み込みが遅い <script> があり、読み込み中にページがランダムにブロックされる場合。
  • ページを定期的にブロックする setInterval などの API 呼び出しですか?

イベント リスナーから blockFor を削除して、setInterval() に追加してみます。

コード全体を見る: input_delay.html

setInterval(() => {
  blockFor
(1000);
}, 3000);


button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

どうなるでしょうか。

8. 入力遅延テストの結果

コード全体を見る: input_delay.html

setInterval(() => {
  blockFor
(1000);
}, 3000);


button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
});

setInterval ブロックタスクの実行中に発生したボタンクリックを記録すると、インタラクション自体でブロック作業が行われていなくても、インタラクションが長時間実行されます。

このような長時間実行期間は、多くの場合、長時間タスクと呼ばれます。

DevTools でインタラクションにカーソルを合わせると、インタラクション時間が処理時間ではなく入力遅延に主に起因していることがわかります。

DevTools の [パフォーマンス] パネル。1 秒のブロック タスク、そのタスクの途中で発生したインタラクション、642 ミリ秒のインタラクション(主にインプット遅延によるもの)が表示されている

必ずしもインタラクションに影響するとは限りません。タスクの実行中にクリックしなかった場合は、運が良ければ成功する可能性があります。このような「ランダム」なくしゃみは、問題がときどきしか発生しない場合、デバッグが非常に困難になります。

これらの原因を特定する方法の 1 つは、長いタスク(または長いアニメーション フレーム)と合計ブロック時間を測定することです。

9. プレゼンテーションが遅い

これまで、入力遅延やイベント リスナーを通じて JavaScript のパフォーマンスを見てきましたが、次のペイントのレンダリングに影響を与えるものは他に何があるでしょうか?

高価なエフェクトでページを更新します。

ページの更新がすぐに反映されたとしても、ブラウザはレンダリングに多くの処理を必要とする可能性があります。

メインスレッドで:

  • 状態の変化後に更新をレンダリングする必要がある UI フレームワーク
  • DOM の変更や、コストの高い CSS クエリ セレクタの切り替えを多数行うと、スタイル、レイアウト、ペイントが大量にトリガーされる可能性があります。

メインスレッド以外:

  • CSS を使用して GPU エフェクトを強化する
  • 非常に大きな高解像度画像を追加する
  • SVG/Canvas を使用して複雑なシーンを描画する

ウェブでのレンダリングのさまざまな要素のスケッチ

RenderingNG

ウェブ上でよく見られる例を以下に示します。

  • リンクをクリックした後、最初の視覚的なフィードバックを提供するために一時停止することなく、DOM 全体を再構築する SPA サイト。
  • 動的なユーザー インターフェースで複雑な検索フィルタを提供する検索ページ。ただし、そのために高コストのリスナーを実行している。
  • ページ全体のスタイル/レイアウトをトリガーするダークモードの切り替え

10. Experiment: presentation delay

requestAnimationFrameのパフォーマンス低下

requestAnimationFrame() API を使用して、プレゼンテーションの長い遅延をシミュレートしてみましょう。

blockFor 呼び出しを requestAnimationFrame コールバックに移動して、イベント リスナーが戻ったに実行されるようにします。

コード全体を表示: presentation_delay.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  requestAnimationFrame
(() => {
    blockFor
(1000);
 
});
});

どうなるでしょうか。

11. 表示の遅延テストの結果

コード全体を表示: presentation_delay.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();
  requestAnimationFrame
(() => {
    blockFor
(1000);
 
});
});

インタラクションは 1 秒のままですが、何が起こったのでしょうか?

requestAnimationFrame は、次のペイントのにコールバックをリクエストします。INP はインタラクションから次のペイントまでの時間を測定するため、requestAnimationFrameblockFor(1000) は次のペイントを 1 秒間ブロックし続けます。

[パフォーマンス] パネルの 1 秒間のインタラクション

ただし、次の 2 点に注意してください。

  • ホバーすると、メインスレッドのブロックがイベント リスナーの戻り後に発生しているため、すべてのインタラクション時間が「プレゼンテーションの遅延」に費やされていることがわかります。
  • メインスレッド アクティビティのルートは、クリック イベントではなく「アニメーション フレームが発火」になりました。

12. インタラクションの診断

このテストページでは、スコア、タイマー、カウンター UI など、レスポンシブ性が非常に視覚的に表現されていますが、平均的なページをテストする場合は、より微妙な違いになります。

インタラクションが長時間実行される場合、原因が必ずしも明確ではありません。原因は次のいずれかです。

  • 入力遅延?
  • イベント処理の期間はどのくらいですか?
  • 表示の遅延

任意のページで、DevTools を使用してレスポンシブ性を測定できます。習慣にするには、次のフローを試してください。

  1. 通常どおりウェブを閲覧します。
  2. DevTools の [Performance] パネルのライブ指標ビューで、[Interactions] ログを確認します。
  3. パフォーマンスの低いインタラクションが表示された場合は、そのインタラクションを繰り返してみてください。
  • 再現できない場合は、インタラクション ログを使用して分析情報を取得します。
  • 再現できる場合は、[パフォーマンス] パネルでトレースを記録します。

すべての遅延

これらの問題の一部をページに追加してみましょう。

コード全体を見る: all_the_things.html

setInterval(() => {
  blockFor
(1000);
}, 3000);

button
.addEventListener('click', () => {
  blockFor
(1000);
  score
.incrementAndUpdateUI();

  requestAnimationFrame
(() => {
    blockFor
(1000);
 
});
});

次に、コンソールとパフォーマンス パネルを使用して問題を診断します。

13. 試験運用: 非同期処理

インタラクション内でネットワーク リクエストの作成、タイマーの開始、グローバル状態の更新などの非視覚効果を開始できるため、それらの効果が最終的にページを更新するとどうなるでしょうか?

ブラウザが新しいレンダリング更新を実際には必要ないと判断した場合でも、インタラクション後の次のペイントがレンダリングを許可されている限り、インタラクションの測定は停止します。

これを試すには、クリック リスナーから UI の更新を続行し、タイムアウトからブロッキング作業を実行します。

コード全体を見る: timeout_100.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  setTimeout
(() => {
    blockFor
(1000);
 
}, 100);
});

どうなりますか?

14. 非同期処理のテスト結果

コード全体を見る: timeout_100.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  setTimeout
(() => {
    blockFor
(1000);
 
}, 100);
});

1 秒のタスクがトレースの後半で発生するようになった 27 ミリ秒のインタラクション

UI の更新後すぐにメインスレッドが利用可能になるため、インタラクションは短くなります。長いブロッキング タスクは引き続き実行されますが、ペイントの後に実行されるため、ユーザーは UI のフィードバックをすぐに受け取ることができます。

教訓: 削除できないなら、せめて移動させよう。

メソッド

固定の 100 ミリ秒の setTimeout よりも優れた方法はないでしょうか?コードはできるだけ早く実行したいはずです。そうでなければ、削除すればよいのです。

目標:

  • インタラクションで incrementAndUpdateUI() が実行されます。
  • blockFor() はできるだけ早く実行されますが、次のペイントはブロックされません。
  • これにより、「魔法のタイムアウト」なしで予測可能な動作が実現します。

この目的を達成する方法としては、次のようなものがあります。

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

"requestPostAnimationFrame"

requestAnimationFrame 単体(次のペイントの前に実行しようとし、通常はインタラクションが遅くなります)とは異なり、requestAnimationFrame + setTimeoutrequestPostAnimationFrame のシンプルなポリフィルとなり、次のペイントの後にコールバックを実行します。

コード全体: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame
(() => {
    setTimeout
(callback, 0);
 
});
}

button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  afterNextPaint
(() => {
    blockFor
(1000);
 
});
});

人間工学の観点から、Promise でラップすることもできます。

コード全体: raf+task2.html

async function nextPaint() {
 
return new Promise(resolve => afterNextPaint(resolve));
}

button
.addEventListener('click', async () => {
  score
.incrementAndUpdateUI();

  await nextPaint
();
  blockFor
(1000);
});

15. 複数の操作(および rage click)

長時間ブロックする作業を回避することはできますが、長時間実行されるタスクはページをブロックし、今後のインタラクションや他の多くのページ アニメーションや更新にも影響します。

ページの非同期ブロッキング版をもう一度試してください(または、最後のステップで独自の遅延処理のバリエーションを考案した場合は、そのバリエーションを試してください)。

コード全体を見る: timeout_100.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  setTimeout
(() => {
    blockFor
(1000);
 
}, 100);
});

複数回クリックするとどうなりますか?

パフォーマンス トレース

クリックごとに 1 秒間のタスクがキューに登録され、メインスレッドがかなりの時間ブロックされます。

メインスレッドで数秒かかるタスクが複数あり、インタラクションが 800 ミリ秒もかかる

これらの長いタスクが新しいクリックと重複すると、イベント リスナー自体はほぼすぐに戻るにもかかわらず、インタラクションが遅くなります。入力遅延に関する以前の実験と同じ状況を作成しました。ただし、今回は入力遅延の原因は setInterval ではなく、以前のイベント リスナーによってトリガーされた作業です。

戦略

理想的には、長いタスクを完全に削除したいところです。

  • 不要なコード(特にスクリプト)をすべて削除します。
  • 長いタスクの実行を回避するようにコードを最適化します。
  • 新しいインタラクションが届いたときに古い作業を中止します。

16. 戦略 1: デバウンス

クラシックな戦略です。インタラクションが連続して発生し、処理やネットワーク効果のコストが高い場合は、キャンセルして再開できるように、わざと作業の開始を遅らせます。このパターンは、予測入力フィールドなどのユーザー インターフェースで役立ちます。

  • setTimeout を使用して、高コストの作業の開始を遅らせます。タイマー(500 ~ 1,000 ミリ秒など)を使用します。
  • その際にタイマー ID を保存します。
  • 新しいインタラクションが届いたら、clearTimeout を使用して前のタイマーをキャンセルします。

コード全体を見る: debounce.html

let timer;
button
.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

 
if (timer) {
    clearTimeout
(timer);
 
}
  timer
= setTimeout(() => {
    blockFor
(1000);
 
}, 1000);
});

パフォーマンス トレース

複数のインタラクションがあるが、その結果として 1 つの長いタスクのみが発生する

複数回クリックしても、実行される blockFor タスクは 1 つだけです。クリックが 1 秒間なかったら実行されるまで待機します。テキスト入力や、複数のクリックが短時間で発生することが想定されるアイテム ターゲットなど、バースト的に発生するインタラクションには、この戦略をデフォルトで使用するのが理想的です。

17. 戦略 2: 長時間実行中の作業を中断する

ただし、デバウンス期間が経過した直後にクリックが発生し、その長いタスクの途中で処理されて、入力遅延により非常に遅いインタラクションになる可能性はあります。

理想的には、タスクの途中でインタラクションが発生した場合、忙しい作業を一時停止して、新しいインタラクションをすぐに処理できるようにします。どのようにすればよいですか?

isInputPending などの API もありますが、一般的には長いタスクをチャンクに分割する方がよいでしょう

多数の setTimeout

最初の試み: 簡単なことを行う。

コード全体を見る: small_tasks.html

button.addEventListener('click', () => {
  score
.incrementAndUpdateUI();

  requestAnimationFrame
(() => {
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
    setTimeout
(() => blockFor(100), 0);
 
});
});

ブラウザで各タスクを個別にスケジュール設定できるようにすることで、入力の優先度を高くすることができます。

複数のやり取りがあるが、スケジュールされた作業はすべて多くの小さなタスクに分割されている

5 回のクリックで 5 秒の作業に戻りますが、クリックごとの 1 秒のタスクは 10 個の 100 ミリ秒のタスクに分割されています。その結果、複数のインタラクションがこれらのタスクと重複していても、100 ミリ秒を超える入力遅延は発生しません。ブラウザは、setTimeout ワークよりも受信イベント リスナーを優先するため、インタラクションは応答性を維持します。

この戦略は、アプリケーションの読み込み時に呼び出す必要がある独立した機能が多数ある場合など、別々のエントリ ポイントをスケジュールする場合に特に有効です。スクリプトを読み込んでスクリプト評価時にすべてを実行すると、デフォルトで巨大な長いタスクですべてが実行される可能性があります。

ただし、この戦略は、共有状態を使用する for ループなど、密結合されたコードを分割する場合にはうまく機能しません。

yield() を利用できるようになりました

ただし、最新の asyncawait を活用することで、任意の JavaScript 関数に「yield ポイント」を簡単に追加できます。

次に例を示します。

コード全体: yieldy.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

以前と同様に、作業のチャンクの後にメインスレッドが生成され、ブラウザは受信したインタラクションに応答できますが、必要なのは個別の setTimeout ではなく await schedulerDotYield() だけになりました。これにより、for ループの途中でも使用できるほど人間工学的に優れています。

AbortContoller() を利用できるようになりました

この方法は機能しましたが、新しいやり取りが発生して、実行する必要がある作業が変更された場合でも、各やり取りでより多くの作業がスケジュールされます。

デバウンス戦略では、新しいインタラクションごとに以前のタイムアウトをキャンセルしました。ここで同様のことはできますか?これを行う方法の 1 つは、AbortController() を使用することです。

完全なコード: aborty.html を参照

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

クリックが発生すると、blockInPiecesYieldyAborty for ループが開始され、必要な処理が実行されます。同時に、メインスレッドが定期的に譲渡されるため、ブラウザは新しい操作に応答し続けます。

2 回目のクリックが入力されると、最初のループは AbortController でキャンセルされたものとしてフラグが立てられ、新しい blockInPiecesYieldyAborty ループが開始されます。最初のループが次に実行されるようにスケジュールされたとき、signal.abortedtrue になっていることに気づき、それ以上の処理を行わずにすぐに戻ります。

メインスレッドの作業が細かく分割され、インタラクションが短くなり、作業が必要な時間だけ続くようになりました

18. まとめ

すべての長いタスクを分割すると、サイトが新しい操作に反応できるようになります。これにより、初期のフィードバックを迅速に提供できるほか、進行中の作業を中止するなどの判断も行えます。場合によっては、エントリ ポイントを個別のタスクとしてスケジュール設定することもあります。場合によっては、都合のよい場所に「yield」ポイントを追加することもあります。

重要

  • INP はすべてのインタラクションを測定します。
  • 各インタラクションは、入力から次のペイントまで測定されます。これは、ユーザーが応答性を認識する方法です。
  • 入力遅延、イベント処理時間、プレゼンテーション遅延は、すべてインタラクションの応答性に影響します。
  • DevTools を使用すると、INP とインタラクションの内訳を簡単に測定できます。

戦略

  • ページに長時間実行されるコード(長いタスク)がないようにします。
  • 不要なコードをイベント リスナーから次のペイントまで移動します。
  • レンダリングの更新自体がブラウザにとって効率的であることを確認します。

その他の情報