import-on-interaction
クリックやホバー、フォーム入力などのユーザー操作が発生した際に、重いコンポーネントやライブラリを遅延読み込みするテクニックを解説します。初期ロード時には不要なリソースのパフォーマンス最適化を行いたい場合に活用してください。
description の原文を見る
Teaches interaction-based lazy loading for non-critical resources. Use when you have heavy components or libraries that are only needed after user interaction like clicks, hovers, or form input.
SKILL.md 本文
インポート・オン・インタラクション
目次
ページに含まれるコンポーネントやリソースのコードやデータが、必ずしも即座には必要でないことがあります。例えば、ユーザーがページの特定の部分をクリックやスクロールしない限り、ユーザーインターフェースの一部は見えません。これは自分たちが執筆した多くの種類のファーストパーティコードに適用できますが、ビデオプレーヤーやチャットウィジェットなどのサードパーティウィジェットにも適用できます。これらは通常、メインインターフェースを表示するためにボタンをクリックする必要があります。
いつ使うか
- ロードに負荷がかかるサードパーティウィジェット(ビデオプレーヤー、チャットウィジェット)がある場合に使用する
- これは非クリティカルなコードをユーザーが実際に必要とするまで遅延させるのに役立つ
- First Input Delay (FID) と Time to Interactive (TTI) を改善するために使用する
いつ使わないか
- リソースがページロード時に即座に必要で、ユーザーのインタラクションの背後に隠されていない場合
- インタラクション後のロード遅延が目立つほど悪いユーザー体験をもたらす場合(代わりに prefetch/preload を検討)
- 動的インポートのオーバーヘッドが遅延から得られるメリットを上回る小さなモジュールの場合
使い方
- ユーザーのインタラクション(クリック、ホバーなど)時にモジュールをロードするために動的
import()を使用する - 重いサードパーティのエンベッドに対して、フェサード(軽量なプレースホルダー)を実装する
- ファーストパーティコードの場合は、可能な限り import-on-interaction よりも prefetch を優先する
- ホバー時に必要なオリジンへの preconnect を検討して、レイテンシーを削減する
- React では React.lazy を Suspense と組み合わせてコンポーネントレベルの import-on-interaction を使用する
詳細
これらのリソースをすぐに(つまり最初から)ロードすると、それらが負荷がある場合 メインスレッドをブロック し、ユーザーがページのより重要な部分と対話できるようになるまでの時間を遅延させる可能性があります。これは First Input Delay、Total Blocking Time、Time to Interactive などのインタラクション準備メトリクスに影響を与える可能性があります。これらのリソースを即座にロードする代わりに、以下のようなより適切なタイミングでロードできます:
- ユーザーが初めてそのコンポーネントと対話するためにクリックするとき
- コンポーネントがビューにスクロールされたとき
- またはそのコンポーネントのロードをブラウザがアイドル状態になるまで遅延させる(requestIdleCallback 経由)
リソースをロードする異なる方法は、高レベルでは以下の通りです:
- Eager - リソースをすぐにロード(スクリプトをロードする通常の方法)
- Lazy (ルートベース) - ユーザーがルートまたはコンポーネントに移動するときにロード
- Lazy (インタラクション時) - ユーザーが UI をクリックするときにロード(例:Show Chat)
- Lazy (ビューポート内) - ユーザーがコンポーネントへスクロールするときにロード
- Prefetch - 必要とされる前にロード(ただしクリティカルリソースがロードされた後)
- Preload - 熱心に、より高いレベルの緊急性を持って
インタラクション時にフィーチャーコードを怠惰にインポートするパターンは、多くのコンテキストで使用されています。以前にこれを使用した場所の 1 つは Google Docs です。彼らは共有フィーチャーのロードを 500KB のスクリプトを保存することで、ユーザーインタラクションまでロードを遅延させています。
import-on-interaction が良い適合である別の場所は、サードパーティウィジェットをロードすることです。
フェサードでサードパーティ UI を「フェイク」ロードする
サードパーティスクリプトをインポートしており、それがレンダリング何かやいつコードをロードするかについて制御が少ない場合があります。import-on-interaction を実装するための 1 つのオプションは、単純です:フェサード を使用します。フェサードは、より負荷がかかるコンポーネントの単純な「プレビュー」または「プレースホルダー」であり、画像やスクリーンショットなどで基本的な体験をシミュレートします。これは Lighthouse チーム内でこの考えに対して使用していた用語です。
ユーザーが「プレビュー」(フェサード)をクリックすると、リソースのコードがロードされます。これにより、フィーチャーを使用しない場合、ユーザーが経験コストを支払う必要がなくなります。同様に、フェサードは、ホバー時に必要なリソースに preconnect できます。
ビデオプレーヤーエンベッド
「フェサード」の良い例は、Paul Irish による YouTube Lite Embed です。これは YouTube ビデオ ID を取得し、最小限のサムネイルと再生ボタンを表示するカスタム要素を提供します。要素をクリックすると、完全な YouTube エンベッドコードが動的にロードされます。つまり、再生をクリックしないユーザーはそれをフェッチおよび処理するコストを支払いません。
同様の手法は、いくつかの Google サイトで本番環境で使用されています。Android.com では、YouTube ビデオプレーヤーエンベッドをすぐにロードする代わりに、ユーザーに偽のプレーヤーボタン付きサムネイルが表示されます。それをクリックすると、完全な YouTube ビデオプレーヤーエンベッドを使用してビデオを自動再生するモーダルがロードされます。
認証
アプリは、クライアント側の JavaScript SDK を使用してサービスでの認証をサポートする必要がある場合があります。これらは時々大きく、JavaScript 実行コストが重く、ユーザーがログインしない場合は最初から熱心にロードしたくないかもしれません。代わりに、ユーザーが「ログイン」ボタンをクリックするときに認証ライブラリを動的にインポートし、初期ロード中にメインスレッドをより自由に保ちます。
チャットウィジェット
Calibre アプリは、同様のフェサードアプローチの使用を通じて、Intercom ベースのライブチャットのパフォーマンスを 30% 改善しました。彼らは CSS と HTML のみを使用して「フェイク」な高速ロードライブチャットボタンを実装し、クリック時に Intercom バンドルをロードしていました。
Postmark は、彼らのヘルプチャットウィジェットが常にすぐにロードされていたことに気づきました。顧客によってまれにしか使用されていなかったにもかかわらずです。ウィジェットは 314KB のスクリプトをプルインしていました。これはホームページ全体より多いものです。ユーザー体験を改善するために、彼らはウィジェットを HTML と CSS を使用したフェイクレプリカに置き換え、クリック時に本物をロードしていました。この変更により Time to Interactive は 7.7 秒から 3.7 秒に削減されました。
その他
Ne-digital は React ライブラリを使用してアニメーション化されたスクロールを実装しており、ユーザーがページの上部に「scroll to top」ボタンをクリックしたときに戻ります。このボタンで、彼らはこのために react-scroll 依存性をすぐにロードするのではなく、ボタンとのインタラクション時にロードし、約 7KB を保存しています:
handleScrollToTop() {
import('react-scroll').then(scroll => {
scroll.animateScroll.scrollToTop({
})
})
}
import-on-interaction はどのように実装するか?
Vanilla JavaScript
JavaScript では、動的 import() はモジュールの怠惰なロードを有効にし、Promise を返します。正しく適用すると非常に強力です。以下は、ボタンイベントリスナーで動的インポートを使用して lodash.sortby モジュールをインポートしてから使用する例です。
const btn = document.querySelector("button");
btn.addEventListener("click", (e) => {
e.preventDefault();
import("lodash.sortby")
.then((module) => module.default)
.then(sortInput()) // use the imported dependency
.catch((err) => {
console.log(err);
});
});
動的インポート以前または適切でない使用例のために、Promise ベースのスクリプトローダーを使用してページにスクリプトを動的に注入することも選択肢でした:
const loginBtn = document.querySelector("#login");
loginBtn.addEventListener("click", () => {
const loader = new scriptLoader();
loader
.load(["//apis.google.com/js/client:platform.js?onload=showLoginScreen"])
.then(({ length }) => {
console.log(`${length} scripts loaded!`);
});
});
React
<MessageList>、<MessageInput>、および <EmojiPicker> コンポーネント(emoji-mart により搭載、98KB minified and gzipped)を持つ Chat アプリケーションがあると想像してください。初期ページロードでこれらのコンポーネントすべてを熱心にロードするのが一般的です。
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import EmojiPicker from './EmojiPicker';
const Channel = () => {
...
return (
<div>
<MessageList />
<MessageInput />
{emojiPickerOpen && <EmojiPicker />}
</div>
);
};
この作業のロードを分割することは、code-splitting を使用することで比較的単純です。React.lazy メソッドは、動的インポートを使用してコンポーネントレベルで React アプリケーションの code-split を容易にします。React.lazy 関数は、Suspense コンポーネントと組み合わせることで、非常に少ない作業でアプリケーション内のコンポーネントを JavaScript の別々のチャンクに分離するための組み込み方法を提供します。その後、Suspense コンポーネントと組み合わせることで、ロード状態の処理ができます。
import React, { lazy, Suspense } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
const EmojiPicker = lazy(
() => import('./EmojiPicker')
);
const Channel = () => {
...
return (
<div>
<MessageList />
<MessageInput />
{emojiPickerOpen && (
<Suspense fallback={<div>Loading...</div>}>
<EmojiPicker />
</Suspense>
)}
</div>
);
};
このアイデアを拡張して、アプリケーションが最初にロードされるときではなく、<MessageInput> で Emoji アイコンがクリックされるときにのみ Emoji Picker コンポーネントのコードをインポートできます:
import React, { useState, createElement } from "react";
import MessageList from "./MessageList";
import MessageInput from "./MessageInput";
import ErrorBoundary from "./ErrorBoundary";
const Channel = () => {
const [emojiPickerEl, setEmojiPickerEl] = useState(null);
const openEmojiPicker = () => {
import(/* webpackChunkName: "emoji-picker" */ "./EmojiPicker")
.then((module) => module.default)
.then((emojiPicker) => {
setEmojiPickerEl(createElement(emojiPicker));
});
};
const closeEmojiPickerHandler = () => {
setEmojiPickerEl(null);
};
return (
<ErrorBoundary>
<div>
<MessageList />
<MessageInput onClick={openEmojiPicker} />
{emojiPickerEl}
</div>
</ErrorBoundary>
);
};
Vue
Vue.js では、同様の import-on-interaction パターンをいくつかの異なる方法で実現できます。1 つの方法は、関数にラップされた動的インポート(つまり () => import("./Emojipicker"))を使用して Emojipicker Vue コンポーネントを動的にインポートすることです。通常、これを行うことで Vue.js はレンダリング時にコンポーネントを遅延ロードします。
その後、ユーザーインタラクションの背後にある怠惰なロードをゲート化できます。ボタンクリックで切り替える、ピッカーの親 div 上の条件付き v-if を使用することで、ユーザーがクリックするときに Emojipicker コンポーネントを条件付きでフェッチおよびレンダリングできます。
<template>
<div>
<button @click="show = true">Load Emoji Picker</button>
<div v-if="show">
<emojipicker></emojipicker>
</div>
</div>
</template>
<script>
export default {
data: () => ({ show: false }),
components: {
Emojipicker: () => import("./Emojipicker"),
},
};
</script>
import-on-interaction パターンは、Angular を含む、動的コンポーネントロードをサポートする最も多くのフレームワークおよびライブラリで可能です。
ファーストパーティコードの import-on-interaction と段階的ロードの一部
インタラクション時のコードロードは、Google が Flights および Photos などの大規模アプリケーションで段階的ロードを処理する方法の重要な部分でもあります。これを説明するために、Shubhie Panicker によって以前に提示された例を見てみましょう。
ユーザーがムンバイ、インドへの旅行を計画しており、Google Hotels にアクセスして価格を確認すると想像してください。このインタラクションに必要なすべてのリソースを最初から熱心にロードできますが、ユーザーが目的地を選択していない場合、マップに必要な HTML/CSS/JS は不要です。
最も単純なダウンロード シナリオでは、Google Hotels が単純なクライアント側レンダリング(CSR)を使用していると想像してください。すべてのコードが最初にダウンロードおよび処理されます:HTML、その後 JS、CSS、その後データのフェッチ。すべてを持った後にのみレンダリングされます。ただし、これによりユーザーは画面に何も表示されずに長時間待たされます。JavaScript と CSS の大きなチャンクが不要である場合があります。
次に、この体験がサーバー側レンダリング(SSR)に移行したと想像してください。ユーザーがビジュアル的に完全なページをすぐに取得できるようにします。これは素晴らしいですが、データがサーバーからフェッチされ、クライアント フレームワークが hydration を完了するまで、ページはインタラクティブではありません。
SSR は改善の可能性がありますが、ユーザーが不気味な谷の体験をする場合があります。ページの準備ができているように見えますが、何をタップしても能力がない場合があります。これは時々 rage clicks と呼ばれ、ユーザーが繰り返し何度も繰り返しクリックする傾向があります。
Google Hotels の検索例に戻ると、UI を少しズームインすると、ユーザーが「その他のフィルター」をクリックして正確に正しいホテルを見つけると、そのコンポーネントに必要なコードがダウンロードされることがわかります。
最初はごく最小限のコードがダウンロードされ、その先、ユーザーのインタラクションがいつどのコードが送信されるかを決定します。
インタラクション駆動の遅延ロードには、多くの重要な側面があります:
- 最初に、ページがビジュアル的に完全になるように最小限のコードをダウンロードします。
- 次に、ユーザーがページと対話し始めると、それらのインタラクションを使用してロードする他のコードを決定します。例えば、「その他のフィルター」コンポーネントのコードをロードします。
- これは、ユーザーがそれを使用する必要がないため、ページ上の多くの機能のコードがブラウザーに送信されないことを意味します。
早期クリックの喪失をどのように回避するか?
これらの Google チームが使用するフレームワークスタックでは、フレームワークがブートストラップされる前にすべてのクリックを追跡する小さなイベントライブラリ(JSAction)を含む最初の HTML チャンクがあるため、早期クリックを追跡できます。イベントは 2 つのことに使用されます:
- ユーザーインタラクションに基づくコンポーネントコードのダウンロードのトリガー
- フレームワークが bootstrapping を完了するときのユーザーインタラクションの再生
使用できる他の潜在的なヒューリスティクスには、コンポーネントコードのロードが含まれます:
- アイドル時間後の期間
- 関連する UI/ボタン/行動喚起のマウスホバー時
- ブラウザシグナルの勾配スケール(例:ネットワーク速度、Data Saver モード など)に基づいて
データについてはどうか?
ページをレンダリングするために使用される初期データは、初期ページの SSR HTML に含まれ、ストリーム配信されます。後期ロードされるデータは、どのコンポーネントが対応しているかを知っているので、ユーザーインタラクションに基づいてダウンロードされます。
これは import-on-interaction 画像を完成させます。CSS と JS がどのように機能するか同様にデータフェッチが機能します。コンポーネントは必要なコード、データ、リソースを認識しているため、すべてのリソースは常にリクエストから距離を置きません。
ビルド時にコンポーネントと依存性のグラフを作成することで機能します。Web アプリケーションはいつでもこのグラフを参照して、任意のコンポーネント(コードとデータ)に必要なリソースをすばやくフェッチできます。また、ルートではなくコンポーネントに基づいて code-split することを意味します。
上記の例のウォークスルーについては、Elevating the Web Platform with the JavaScript Community を参照してください。
トレードオフ
負荷がかかる作業をユーザーインタラクションに近づけることは、ページの最初のロードをどの程度早くするかを最適化できます。ただし、この手法はトレードオフなしではありません。
ユーザーがクリック後にスクリプトのロードに長時間かかる場合はどうなりますか?
Google Hotels の例では、小さい粒度のチャンクは、ユーザーがコードおよびデータをフェッチして実行するのを待つ可能性を最小化します。他のケースでは、大きな依存性が遅いネットワークでこの懸念を導入する可能性があります。
これが発生する可能性を減らす方法の 1 つは、ページで重要なコンテンツのロードが完了した後、これらのリソースのロードまたは prefetch をより良くの分割することです。アプリケーションでこれへの影響を測定することをお勧めします。
ユーザーインタラクション前の機能の欠如についてはどうか?
フェサードとのもう 1 つのトレードオフは、ユーザーインタラクション前の機能の欠如です。例えば、埋め込まれたビデオプレーヤーはメディアを自動再生できません。そのような機能がキーである場合、ユーザーがそれらをビューにスクロールするときに、インタラクション遅延ロードまではなく、サードパーティの iframe を遅延ロードするなど、リソースをロードするための別のアプローチを検討するかもしれません。
インタラクティブエンベッドを静的バリアントで置き換える
import-on-interaction パターンおよび段階的ロードについて説明しました。エンベッド使用例に対して完全に静的になることはどうですか?
エンベッドの最終的にレンダリングされたコンテンツが、一部のケースでは即座に必要な場合があります。例えば、初期ビューポートに見える社会メディアの投稿です。これは、エンベッドが 2~3MB の JavaScript を入れてくるときに、独自の課題を導入することもできます。エンベッドコンテンツが適切に必要なため、遅延ロードおよびフェサードはより適用可能かもしれません。
パフォーマンスを最適化する場合、エンベッドを同様に見える静的バリアントで完全に置き換え、より対話的なバージョン(例:元の社会メディアの投稿)にリンクすることは可能です。ビルド時に、エンベッドのデータをプルインして、静的な HTML バージョンに変換できます。
静的置換はパフォーマンスに良い場合がありますが、オプションを評価するときに、これを念頭に置いておくこと、多くの場合、カスタムを実行する必要があります。
結論
ファーストパーティ JavaScript は、多くの場合、Web 上の最新ページのインタラクション準備に影響を与えます。しかし、多くの場合、ファーストパーティおよびサードパーティソースの両方から非クリティカルな JS がメインスレッドを忙しくしているネットワークの背後に遅延することが多いです。
一般的に、ドキュメント頭部の同期サードパーティスクリプトを避けて、ファーストパーティ JS のロードが終わった後で非ブロッキング サードパーティスクリプトをロードすることを目指します。import-on-interaction のようなパターンは、非クリティカルなリソースのロードを、ユーザーが非常にそれらをパワー化する UI を必要とする可能性が高いポイントまで遅延させる方法を提供します。
出典
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- patternsdev
- リポジトリ
- patternsdev/skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/patternsdev/skills / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。