Now You See Me:延期、遅延読み込み、IntersectionObserverでの行動方法

公開: 2022-03-10
クイックサマリー↬交差情報は、画像の遅延読み込みなど、さまざまな理由で必要になります。 しかし、それだけではありません。 Intersection Observer APIについて、より深く理解し、さまざまな視点を得るときが来ました。 準備?

昔々、サイトがすべてのブラウザで同じように見えるべきではないことを顧客に納得させ、アクセシビリティを気にし、CSSグリッドを早期に採用したWeb開発者が住んでいました。 しかし、彼の心の奥底では、彼の真の情熱はパフォーマンスでした。彼は常に最適化、縮小、監視を行い、プロジェクトで心理的なトリックを採用していました。

その後、ある日、ユーザーにはすぐには表示されず、画面に意味のあるコンテンツをレンダリングするために不可欠ではない、遅延読み込みの画像やその他のアセットについて学びました。 それは夜明けの始まりでした。開発者は、遅延読み込みjQueryプラグインの邪悪な世界(または、 async属性とdefer属性のそれほど邪悪ではない世界)に入りました。 彼がすべての悪の核心、つまりscrollイベントリスナーの世界にまっすぐ入ったとさえ言う人もいます。 彼がどこにたどり着いたかはわかりませんが、この開発者は完全に架空のものであり、開発者との類似点は偶然です。

Web開発者
架空のWeb開発者

さて、あなたは今、パンドラの箱が開かれ、私たちの架空の開発者が問題をそれほど現実的にしないと言うことができます。 今日では、速度とページの重みの両方の観点から、上記のコンテンツを優先することがWebプロジェクトのパフォーマンスにとって非常に重要になりました。

ジャンプした後もっと! 以下を読み続けてください↓

この記事では、 scrollの暗闇から抜け出し、リソースを遅延ロードする最新の方法について説明します。 画像を遅延読み込みするだけでなく、そのためのアセットを読み込みます。 さらに、今日お話しする手法は、アセットの遅延読み込みだけではありません。要素のユーザーへの可視性に基づいて、あらゆるタイプの遅延機能を提供できるようになります。

IntersectionObserver:今あなたは私を見る

ご列席の皆様、Intersection ObserverAPIについてお話ししましょう。 しかし、始める前に、 IntersectionObserverにたどり着いた最新のツールの風景を見てみましょう。

2017年は、ブラウザーに組み込まれたツールにとって非常に良い年であり、あまり労力をかけずにコードベースの品質とスタイルを改善するのに役立ちました。 最近、Webは、オブザーバーインターフェイス(または単に「オブザーバー」)のより明確なアプローチへの非常に典型的な解決とは非常に異なることに基づく散発的なソリューションから離れつつあるようです。最新のブラウザで採用:

  • IntersectionObserverと
  • PerformanceObserver(パフォーマンスタイムラインレベル2仕様の一部として)。

もう1人の潜在的な家族の一員であるFetchObserverは進行中の作業であり、ネットワークプロキシの領域に私たちをさらに導きますが、今日は代わりにフロントエンドについてもっと話したいと思います。

IntersectionObserverとPerformanceObserverは、Observersファミリーの新しいメンバーです。
IntersectionObserverとPerformanceObserverは、Observersファミリーの新しいメンバーです。

PerformanceObserverIntersectionObserverは、フロントエンド開発者がさまざまな時点でプロジェクトのパフォーマンスを向上させるのを支援することを目的としています。 前者はRealUser Monitoringのツールを提供し、後者は具体的なパフォーマンスの向上を提供するツールです。 前に述べたように、この記事では後者の詳細を詳しく見ていきます: IntersectionObserver 。 特にIntersectionObserverの仕組みを理解するには、一般的なObserverが最新のWebでどのように機能するかを確認する必要があります。

上級者向けのヒント:理論をスキップして、IntersectionObserverの仕組みにすぐに飛び込むか、さらには、 IntersectionObserverの可能なアプリケーションに直接進むことができます。

オブザーバー対イベント

「オブザーバー」は、その名前が示すように、ページのコンテキストで発生する何かを監視することを目的としています。 オブザーバーは、DOMの変更など、ページで発生していることを監視できます。 また、ページのライフサイクルイベントを監視することもできます。 オブザーバーは、いくつかのコールバック関数を実行することもできます。 今、注意深い読者はここで問題をすぐに見つけて、こう尋ねるかもしれません。 この目的のためのイベントはもうありませんか? オブザーバーの違いは何ですか?」 とても良い点です! よく見て、整理しましょう。

オブザーバーとイベント:違いは何ですか?
オブザーバーとイベント:違いは何ですか?

通常のイベントとオブザーバーの決定的な違いは、デフォルトでは、前者はイベントの発生ごとに同期的に反応し、メインスレッドの応答性に影響を与えますが、後者はパフォーマンスにそれほど影響を与えずに非同期的に反応する必要があることです。 少なくとも、これは現在提示されているオブザーバーには当てはまります。それらはすべて非同期で動作し、将来変更されることはないと思います。

これは、初心者を混乱させる可能性のあるオブザーバーのコールバックの処理における主な違いにつながります。オブザーバーの非同期性により、複数のオブザーバブルが同時にコールバック関数に渡される可能性があります。 このため、コールバック関数は単一のエントリではなく、エントリの配列を期待する必要があります(ただし、 Arrayにエントリが1つしかない場合もあります)。

さらに、一部のオブザーバー(特に今日話しているもの)は、非常に便利な事前計算されたプロパティを提供します。それ以外の場合は、通常のイベントを使用するときに、高価な(パフォーマンスの観点から)メソッドとプロパティを使用して自分自身を計算していました。 この点を明確にするために、この記事の後半で例を示します。

したがって、誰かがイベントパラダイムから離れるのが難しい場合、オブザーバーはステロイドのイベントであると言えます。 別の説明は次のようになります。オブザーバーは、イベントに加えて新しいレベルの近似値です。 ただし、どちらの定義を選択しても、オブザーバーがイベントを置き換えることを意図していないことは言うまでもありません(少なくともまだ)。 両方に十分なユースケースがあり、それらは共存して幸せに暮らすことができます。

オブザーバーはイベントを置き換えることを意図していません:両方が幸せに一緒に暮らすことができます。
オブザーバーはイベントを置き換えることを意図していません:両方が幸せに一緒に暮らすことができます。

一般的なオブザーバーの構造

オブザーバーの一般的な構造(執筆時点で利用可能なもののいずれか)は、次のようになります。

 /** * Typical Observer's registration */ let observer = new YOUR-TYPE-OF-OBSERVER(function (entries) { // entries: Array of observed elements entries.forEach(entry => { // Here we can do something with each particular entry }); }); // Now we should tell our Observer what to observe observer.observe(WHAT-TO-OBSERVE);

繰り返しになりますが、 entriesは値のArrayであり、単一のエントリではないことに注意してください。

これは一般的な構造です。特定のオブザーバーの実装は、 observe()に渡される引数と、コールバックに渡される引数が異なります。 たとえば、 MutationObserverは、構成オブジェクトを取得して、監視するDOMの変更について詳しく知る必要があります。 PerformanceObserverはDOM内のノードを監視しませんが、代わりに監視できるエントリタイプの専用セットを備えています。

ここで、このディスカッションの「一般的な」部分を終了し、今日の記事のトピックであるIntersectionObserverについて詳しく見ていきましょう。

IntersectionObserverの脱構築

IntersectionObserverの脱構築
IntersectionObserverの脱構築

まず、 IntersectionObserverとは何かを整理しましょう。

MDNによると:

Intersection Observer APIは、ターゲット要素と祖先要素またはトップレベルドキュメントのビューポートとの交差の変化を非同期的に監視する方法を提供します。

簡単に言えば、 IntersectionObserverは、ある要素と別の要素のオーバーラップを非同期的に監視します。 IntersectionObserverでこれらの要素が何のためにあるかについて話しましょう。

IntersectionObserverの初期化

前の段落の1つで、一般的なオブザーバーの構造を見てきました。 IntersectionObserverは、この構造を少し拡張します。 まず、このタイプのオブザーバーには、次の3つの主要な要素を含む構成が必要です。

  • root :これは観測に使用されるルート要素です。 これは、観察可能な要素の基本的な「キャプチャフレーム」を定義します。 デフォルトでは、 rootはブラウザのビューポートですが、実際にはDOM内の任意の要素にすることができます(次に、 rootdocument.getElementById('your-element')ようなものに設定します)。 ただし、この場合、監視する要素はrootのDOMツリーに「存在」する必要があることに注意してください。
IntersectionObserverの構成のrootのプロパティ
rootプロパティは、要素の「キャプチャフレーム」のベースを定義します。
  • rootMarginrootの寸法が十分な柔軟性を提供しない場合に、「キャプチャフレーム」を拡張または縮小するroot要素の周囲のマージンを定義します。 この構成の値のオプションは、 rootMargin: '50px 20px 10px 40px' (上、右下、左)などのCSSのmarginのオプションと同様です。 値は省略可能で( rootMargin: '50px'ように)、 pxまたは%のいずれかで表すことができます。 デフォルトでは、 rootMargin: '0px'
IntersectionObserverの構成のrootMarginプロパティ
rootMarginプロパティは、 rootによって定義される「キャプチャフレーム」を展開/縮小します。
  • threshold :観察された要素が「キャプチャフレーム」( rootrootMarginの組み合わせとして定義される)の境界と交差するときに、常に即座に反応する必要はありません。 thresholdは、オブザーバーが反応する必要があるそのような交差のパーセンテージを定義します。 単一の値または値の配列として定義できます。 thresholdの効果をよりよく理解するために(私はそれが時々混乱するかもしれないことを知っています)、ここにいくつかの例があります:
    • threshold: 0デフォルト値のIntersectionObserverは、観測された要素の最初または最後のピクセルが「キャプチャフレーム」の境界の1つと交差したときに反応する必要があります。 IntersectionObserverは方向に依存しないことに注意してください。つまり、a)要素が「キャプチャフレーム」に入るときとb)要素が「キャプチャフレーム」から出るときの両方のシナリオで反応します。
    • threshold: 0.5 :観測された要素の50%が「キャプチャフレーム」と交差したときにオブザーバーを起動する必要があります。
    • threshold: [0, 0.2, 0.5, 1] :オブザーバーは4つのケースで反応する必要があります:
      • 観察された要素の最初のピクセルが「キャプチャフレーム」に入ります。要素がまだ実際にはそのフレーム内にないか、観察された要素の最後のピクセルが「キャプチャフレーム」を離れます。要素はフレーム内にありません
      • 要素の20%が「キャプチャフレーム」内にあります(ここでも、 IntersectionObserverでは方向は重要ではありません)。
      • 要素の50%が「キャプチャフレーム」内にあります。
      • 要素の100%が「キャプチャフレーム」内にあります。 これは、 threshold: 0とは正反対です。
IntersectionObserverの構成のしきい値プロパティ
thresholdプロパティは、オブザーバーが起動される前に要素が「キャプチャフレーム」と交差する量によって定義されます。

IntersectionObserverに目的の構成を通知するには、次のようなコールバック関数とともにconfigオブジェクトをObserverのコンストラクターに渡すだけです。

 const config = { root: null, // avoiding 'root' or setting it to 'null' sets it to default value: viewport rootMargin: '0px', threshold: 0.5 }; let observer = new IntersectionObserver(function(entries) { … }, config);

ここで、 IntersectionObserverに実際に監視する要素を指定する必要があります。 これは、要素をobserve()関数に渡すだけで実行されます。

 … const img = document.getElementById('image-to-observe'); observer.observe(image);

この観察された要素について注意すべきいくつかの事柄:

  • これは前に説明しましたが、もう一度言及する価値があります。 rootをDOMの要素として設定する場合、監視対象の要素はrootのDOMツリー内に配置する必要があります。
  • IntersectionObserverは、一度に1つの要素のみを観測に受け入れることができ、観測のバッチ供給をサポートしていません。 つまり、複数の要素(たとえば、ページ上の複数の画像)を観察する必要がある場合は、それらすべてを反復処理して、それぞれを個別に観察する必要があります。
 … const images = document.querySelectorAll('img'); images.forEach(image => { observer.observe(image); });
  • Observerが配置されたページをロードすると、 IntersectionObserverのコールバックがすべての監視対象要素に対して一度に発生したことに気付く場合があります。 提供された構成と一致しないものでも。 「まあ…本当に期待していたことではない」というのは、これを初めて体験したときのいつもの思いです。 ただし、ここで混乱しないでください。これは、ページの読み込み中に、これらの観察された要素が何らかの形で「キャプチャフレーム」と交差することを必ずしも意味するわけではありません。
IntersectionObserverがすべての要素に対して一度に起動されるDevToolsのスクリーンショット。
IntersectionObserverは、登録されるとすべての監視対象要素に対して起動されますが、すべてが「キャプチャフレーム」と交差することを意味するわけではありません。

ただし、この要素のエントリが初期化され、 IntersectionObserverによって制御されるようになったことを意味します。 ただし、これによりコールバック関数に不要なノイズが追加される可能性があり、どの要素が実際に「キャプチャフレーム」と交差し、どの要素を考慮する必要がないかを検出するのはユーザーの責任になります。 その検出を行う方法を理解するために、コールバック関数の構造をもう少し深く理解し、そのようなエントリが何で構成されているかを見てみましょう。

IntersectionObserverコールバック

まず、 IntersectionObserverのコールバック関数は2つの引数を取ります。これらについては、 2番目の引数から逆の順序で説明します。 前述の観察されたエントリのArrayとともに、「キャプチャフレーム」と交差して、コールバック関数は2番目の引数としてオブザーバー自体を取得します。

オブザーバー自体への参照

new IntersectionObserver(function(entries, SELF) {…});

オブザーバー自体への参照を取得することは、 IntersectionObserverによって初めて検出された後に要素の監視を停止したい場合に、多くのシナリオで役立ちます。 画像の遅延読み込み、他のアセットの遅延フェッチなどのシナリオは、この種のものです。 要素の監視を停止する場合、 IntersectionObserverは、監視対象の要素に対していくつかのアクション(たとえば、画像の実際の遅延読み込みなど)を実行した後、コールバック関数で実行できるunobserve(element-to-stop-observing)メソッドを提供します。 )。

これらのシナリオのいくつかは、記事でさらに検討されますが、この2番目の議論が邪魔にならないように、このコールバックプレイの主なアクターに取り掛かりましょう。

IntersectionObserverEntry

 new IntersectionObserver(function(ENTRIES, self) {…});

Arrayとしてコールバック関数に取得するentriesは、 IntersectionObserverEntryという特殊なタイプです。 このインターフェースは、特定の観測された各要素に関する事前定義および事前計算された一連のプロパティを提供します。 最も興味深いものを見てみましょう。

まず、 IntersectionObserverEntryタイプのエントリには、プロセスに関係する要素の座標と境界を定義する3つの異なる長方形に関する情報が含まれています。

  • rootBounds :「キャプチャフレーム」の長方形( root + rootMargin );
  • boundingClientRect :観測された要素自体の長方形。
  • IntersectionRect:観察された要素とintersectionRectする「キャプチャフレーム」の領域。
IntersectionObserverEntryの長方形
IntersectionObserverEntryに含まれるすべての外接長方形が自動的に計算されます。

これらの長方形が非同期で計算されることの本当にすばらしい点は、 getBoundingClientRect()offsetTopoffsetLeft 、およびレイアウトのスラッシングをトリガーするその他の高価なポジショニングプロパティやメソッドを呼び出さなくても、要素のポジショニングに関連する重要な情報が得られることです。 パフォーマンスのための純粋な勝利!

私たちにとって興味深いIntersectionObserverEntryインターフェイスのもう1つのプロパティは、 isIntersectingです。 これは、観察された要素が現在「キャプチャフレーム」と交差しているかどうかを示す便利なプロパティです。 もちろん、 intersectionRect (この長方形が0×0でない場合、要素は「キャプチャフレーム」と交差しています)を調べることでこの情報を取得できますが、これを事前に計算しておくと非常に便利です。

isIntersectingを使用して、観測された要素が「キャプチャフレーム」に入ったばかりか、すでに出ているかを確認できます。 これを見つけるには、このプロパティの値をグローバルフラグとして保存し、この要素の新しいエントリがコールバック関数に到着したら、新しいisIntersectingをそのグローバルフラグと比較します。

  • それがfalseで、現在はtrueの場合、要素は「キャプチャフレーム」に入っています。
  • それが反対で、以前はtrueであったのに今はfalseである場合、要素は「キャプチャフレーム」を離れています。

isIntersectingは、前に説明した問題を解決するのに役立つプロパティです。つまり、「キャプチャフレーム」と実際に交差する要素のエントリを、エントリの初期化だけのノイズから分離します。

 let isLeaving = false; let observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { // we are ENTERING the "capturing frame". Set the flag. isLeaving = true; // Do something with entering entry } else if (isLeaving) { // we are EXITING the "capturing frame" isLeaving = false; // Do something with exiting entry } }); }, config);

:Microsoft Edge 15では、 isIntersectingプロパティが実装されておらず、 IntersectionObserverが完全にサポートされているにもかかわらず、 undefinedが返されます。 これは2017年7月に修正され、Edge16以降で利用可能です。

IntersectionObserverEntryインターフェイスは、事前に計算されたもう1つの便利なプロパティintersectionRatioを提供します。 このパラメーターは、 isIntersectingと同じ目的で使用できますが、ブール値ではなく浮動小数点数であるため、よりきめ細かい制御を提供します。 crossenceRatioの値は、観測された要素の領域のどれだけが「キャプチャフレーム」とintersectionRatioしているかを示します( intersectionRect領域とboundingClientRect領域の比率)。 繰り返しになりますが、これらの長方形からの情報を使用して自分でこの計算を行うこともできますが、それを行っておくのは良いことです。

もう見覚えがありませんか?はい、<code> intersectionRatio </ code>プロパティはObserverの設定の<code> threshold </ code>プロパティに似ています。違いは、後者がObserverを起動する<em> when </ em>を定義し、前者が実際の交差点の状況を示すことです(Observerの非同期性により、<code> threshold </ code>とは少し異なります)。
もう見覚えがありませんか? はい、 intersectionRatioプロパティは、Observerの構成のthresholdプロパティに似ています。 違いは、後者がオブザーバーを起動する*タイミング*を定義し、前者が実際の交差点の状況を示すことです(オブザーバーの非同期性により、 thresholdとはわずかに異なります)。

targetは、 IntersectionObserverEntryインターフェイスのもう1つのプロパティであり、頻繁にアクセスする必要があります。 ただし、ここには魔法はまったくありません。これは、オブザーバーのobserve()関数に渡された元の要素にすぎません。 イベントを操作するときに慣れているevent.targetと同じように。

IntersectionObserverEntryインターフェイスのプロパティの完全なリストを取得するには、仕様を確認してください。

可能なアプリケーション

この章のおかげで、おそらくこの記事にたどり着いたと思います。結局、コピーアンドペースト用のコードスニペットがある場合、誰がメカニズムを気にするのでしょうか。 ですから、これ以上の議論に煩わされることはありません。コードと例の領域に入ります。 コードに含まれているコメントが物事をより明確にすることを願っています。

延期された機能

まず、 IntersectionObserverのアイデアの根底にある基本原則を明らかにする例を確認しましょう。 画面に表示されたら、多くの計算を実行する必要がある要素があるとします。 たとえば、広告は、実際にユーザーに表示された場合にのみビューを登録する必要があります。 しかし、ここで、ページの最初の画面の下のどこかに自動再生されたカルーセル要素があると想像してみましょう。

アプリケーションの最初の画面の下にあるカルーセル
カルーセルやその他の重労働機能がアプリケーションの範囲内にある場合、すぐにブートストラップ/ロードを開始するのはリソースの無駄です。

一般に、カルーセルの実行は大変な作業です。 通常、JavaScriptタイマー、要素を自動的にスクロールする計算などが含まれます。これらのタスクはすべてメインスレッドをロードし、自動再生モードで実行すると、メインスレッドがいつこのヒットを取得したかを知るのは困難です。 最初の画面でコンテンツの優先順位付けについて話していて、First MeaningfulPaintとTimeTo Interactiveをできるだけ早く実行したい場合、ブロックされたメインスレッドがパフォーマンスのボトルネックになります。

この問題を修正するために、このようなカルーセルの再生をブラウザのビューポートに入るまで延期する場合があります。 この場合、 IntersectionObserverEntryインターフェイスのisIntersectingパラメーターの知識と例を使用します。

 const carousel = document.getElementById('carousel'); let isLeaving = false; let observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { isLeaving = true; entry.target.startCarousel(); } else if (isLeaving) { isLeaving = false; entry.target.stopCarousel(); } }); } observer.observe(carousel);

ここでは、カルーセルがビューポートに入ったときにのみカルーセルを再生します。 IntersectionObserverの初期化に渡されるconfigオブジェクトがないことに注意してください。これは、デフォルトの構成オプションに依存していることを意味します。 カルーセルがビューポートから出たら、重要ではない要素にリソースを費やさないように、カルーセルの再生を停止する必要があります。

アセットの遅延読み込み

これはおそらく、 IntersectionObserverの最も明白なユースケースです。ユーザーが今必要としないものをダウンロードするためにリソースを費やしたくありません。 これにより、ユーザーに大きなメリットがもたらされます。ユーザーはダウンロードする必要がなく、モバイルデバイスは、現時点では必要のない多くの役に立たない情報を解析してコンパイルする必要がありません。 当然のことながら、アプリのパフォーマンスにも役立ちます。

折り目の下の画像の遅延読み込み
最初の画面の下にある画像などの遅延読み込みアセット–IntersectionObserverの最も明白なアプリケーション。

以前は、ユーザーがリソースを画面に表示するまでリソースのダウンロードと処理を延期するために、 scrollなどのイベントのイベントリスナーを処理していました。 問題は明らかです。これにより、リスナーが頻繁にトリガーされました。 そのため、コールバックの実行を抑制またはデバウンスするというアイデアを考え出す必要がありました。 しかし、これらすべてがメインスレッドに大きなプレッシャーを加え、最も必要なときにメインスレッドをブロックしていました。

では、遅延読み込みのシナリオでIntersectionObserverに戻ると、何に注意を払う必要がありますか? 遅延読み込み画像の簡単な例を確認してみましょう。

CodePenのDenysMishunov(@mishunov)によるIntersectionObserverのペン遅延読み込みを参照してください。

CodePenのDenysMishunov(@mishunov)によるIntersectionObserverのペン遅延読み込みを参照してください。

そのページをゆっくりと「3番目の画面」までスクロールして、右上隅の監視ウィンドウを見てください。これまでにダウンロードされた画像の数がわかります。

このタスクのHTMLマークアップのコアには、単純な一連の画像があります。

 … <img data-src="https://blah-blah.com/foo.jpg"> …

ご覧のとおり、画像にはsrcタグが付いていないはずです。ブラウザがsrc属性を確認すると、意図とは逆に、その画像のダウンロードがすぐに開始されます。 したがって、その属性をHTMLの画像に配置するべきではなく、代わりに、ここでdata-srcようなdata-属性に依存する可能性があります。

このソリューションのもう1つの部分は、もちろんJavaScriptです。 ここで主な部分に焦点を当てましょう:

 const images = document.querySelectorAll('[data-src]'); const config = { … }; let observer = new IntersectionObserver(function (entries, self) { entries.forEach(entry => { if (entry.isIntersecting) { … } }); }, config); images.forEach(image => { observer.observe(image); });

構造的には、ここで新しいことは何もありません。これまでにすべてをカバーしました。

  • data-src属性を持つすべてのメッセージを取得します。
  • Set config :このシナリオでは、「キャプチャフレーム」を拡張して、ビューポートの下部より少し低い要素を検出します。
  • IntersectionObserverをその構成に登録します。
  • 画像を繰り返し処理し、このIntersectionObserverで監視されるようにすべての画像を追加します。

興味深い部分は、エントリで呼び出されるコールバック関数内で発生します。 関係する3つの重要なステップがあります。

  1. まず、「キャプチャフレーム」と実際に交差しているアイテムのみを処理します。 このスニペットは、今ではおなじみのはずです。

     entries.forEach(entry => { if (entry.isIntersecting) { … } });

  2. 次に、 data-srcを含む画像を実際の<img src="…">に変換することにより、エントリを処理します。

     if (entry.isIntersecting) { preloadImage(entry.target); … }
    これにより、ブラウザがトリガーされ、最終的に画像がダウンロードされます。 preloadImage()は、ここで言及する価値のない非常に単純な関数です。 ソースを読んでください。

  3. 次の最後のステップ:遅延読み込みは1回限りのアクションであり、要素が「キャプチャフレーム」に入るたびに画像をダウンロードする必要がないため、すでに処理された画像をunobserveしないようにする必要があります。 コード内のメモリリークを防ぐために、通常のイベントが不要になったときにelement.removeEventListener()を使用して行うのと同じ方法です。

     if (entry.isIntersecting) { preloadImage(entry.target); // Observer has been passed as self to our callback self.unobserve(entry.target); }

ノート。 unobserve(event.target)の代わりに、 disconnect()を呼び出すこともできます。これにより、 IntersectionObserverが完全に切断され、画像が観察されなくなります。 これは、気になるのがオブザーバーの最初のヒットだけである場合に役立ちます。 私たちの場合、画像を監視し続けるためにオブザーバーが必要なので、まだ切断しないでください。

自由に例をフォークして、さまざまな設定やオプションで遊んでください。 特に画像を遅延ロードする場合は、興味深い点が1つあります。 観察された要素によって生成されたボックスを常に念頭に置いておく必要があります! 例を確認すると、41〜47行目の画像のCSSに、冗長なスタイルが含まれていることがわかります。 min-height: 100px 。 これは、画像のプレースホルダー( src属性のない<img> )に垂直方向の寸法を与えるために行われます。 何のために?

  • 垂直方向の寸法がない場合、すべての<img>タグは0×0ボックスを生成します。
  • <img>タグはデフォルトである種のinline-blockボックスを生成するため、これらの0×0ボックスはすべて同じ行に並べて配置されます。
  • これは、 IntersectionObserverがすべての画像(または、スクロールの速さによってはほとんどすべての画像)を一度に登録することを意味します。おそらく、達成したいものとはまったく異なります。

現在のセクションのハイライト

もちろん、 IntersectionObserverは単なる遅延読み込み以上のものです。 scrollイベントをこのテクノロジーに置き換える別の例を次に示します。 これには、かなり一般的なシナリオがあります。固定ナビゲーションバーで、ドキュメントのスクロール位置に基づいて現在のセクションを強調表示する必要があります。

CodePenのDenysMishunov(@mishunov)によるIntersectionObserverのペンハイライトの現在のセクションを参照してください。

CodePenのDenysMishunov(@mishunov)によるIntersectionObserverのペンハイライトの現在のセクションを参照してください。

構造的には、画像の遅延読み込みの例に似ており、次の例外を除いて同じ基本構造を持っています。

  • ここで、画像ではなく、ページ上のセクションを観察します。
  • 明らかに、コールバックのエントリを処理するための別の関数( intersectionHandler(entry) )もあります。 しかし、これは興味深いものではありません。CSSクラスを切り替えるだけです。

ここで興味深いのは、 configオブジェクトです。

 const config = { rootMargin: '-50px 0px -55% 0px' };

rootMarginのデフォルト値である0pxを使用しないのはなぜですか? 現在のセクションを強調表示することと画像を遅延読み込みすることは、私たちが達成しようとしていることとはまったく異なるからです。 遅延読み込みでは、画像がビューに入る前に読み込みを開始します。 したがって、その目的のために、下部の「キャプチャフレーム」を50ピクセル拡張しました。 逆に、現在のセクションを強調表示する場合は、そのセクションが実際に画面に表示されていることを確認する必要があります。 そしてそれだけではありません。ユーザーが実際にこのセクションを読んでいるか、読んでいることを確認する必要があります。 したがって、アクティブなセクションとして宣言する前に、セクションを下からビューポートの半分より少し大きくする必要があります。 また、ナビゲーションバーの高さを考慮したいので、「キャプチャフレーム」からバーの高さを削除します。

現在のセクションのキャプチャフレーム
オブザーバーは、上から50pxと下からビューポートの55%の間の「キャプチャフレーム」に入る要素のみを検出する必要があります。

また、現在のナビゲーションアイテムを強調表示する場合、何も監視を停止したくないことに注意してください。 ここでは、常にIntersectionObserverを担当する必要があるため、ここでは、 disconnect()unobserve() )も見つかりません。

概要

IntersectionObserverは、非常に単純なテクノロジーです。 それは現代のブラウザでかなり良いサポートを持っており、それをまだサポートしている(またはまったくサポートしない)ブラウザに実装したい場合は、もちろん、そのためのポリフィルがあります。 しかし、全体として、これは、ビューポート内の要素の検出に関連するあらゆる種類のことを実行できると同時に、非常に優れたパフォーマンスの向上を実現できる優れたテクノロジーです。

IntersectionObserverがあなたに適しているのはなぜですか?

  • IntersectionObserverは非同期のノンブロッキングAPIです!
  • IntersectionObserverは、 scrollまたはresizeイベントで高価なリスナーを置き換えます。
  • IntersectionObserverは、 getClientBoundingRect()のような高価な計算をすべて実行するため、必要はありません。
  • IntersectionObserverは、他のオブザーバーの構造パターンに従うため、理論的には、他のオブザーバーの動作に精通している場合は簡単に理解できるはずです。

心に留めておくべきこと

IntersectionObserverの機能をすべての元のwindow.addEventListener('scroll')世界と比較すると、このオブザーバーで短所を確認するのは困難です。 したがって、代わりに覚えておくべきいくつかのことに注意しましょう。

  • はい、 IntersectionObserverは非同期のノンブロッキングAPIです。 これを知るのは素晴らしいことです! ただし、API自体が非同期であっても、コールバックで実行しているコードはデフォルトでは非同期で実行されないことを理解することがさらに重要です。 したがって、コールバック関数の計算によってメインスレッドが応答しなくなった場合でも、 IntersectionObserverから得られるすべての利点を排除する可能性があります。 しかし、これは別の話です。
  • アセット(画像など)の遅延読み込みにIntersectionObserverを使用している場合は、アセットが読み込まれた後.unobserve(asset)を実行します。
  • IntersectionObserverは、ドキュメントのフォーマット構造に表示される要素の交差のみを検出できます。 明確にするために:観察可能な要素はボックスを生成し、何らかの形でレイアウトに影響を与える必要があります。 理解を深めるための例を次に示します。

    • display: none
    • opacity: 0またはvisibility:hiddenは、これらが検出されるように(非表示であっても)ボックスを作成します。
    • width:0px; height:0px width:0px; height:0pxで問題ありません。 Though, it has to be noted that absolutely positioned elements fully positioned outside of parent's borders (with negative margins or negative top , left , etc.) and are cut out by parent's overflow: hidden won't be detected: their box is out of scope for the formatting structure.
IntersectionObserver: Now You See Me
IntersectionObserver: Now You See Me

I know it was a long article, but if you're still around, here are some links for you to get an even better understanding and different perspectives on the Intersection Observer API:

  • Intersection Observer API on MDN;
  • IntersectionObserver polyfill;
  • IntersectionObserver polyfill as npm module;
  • Lazy-Loading Images with IntersectionObserver [video] by amazing Paul Lewis;
  • Basic and short (just 01:39), but very informative introduction to IntersectionObserver [video] by Surma.

With this, I would like to make a pause in our discussion to give you an opportunity to play with this technology and realize all of its convenience. So, go play with it. The article is finally over. This time I really mean it.