序
会社でgoogleスピードテストの評価を少しでも上げたいと。じゃぁこうしましょうと…
スクロールに追従して、画像の遅延ロードといったことを行いたいが、
このようなケースにおいて、今まではスクロールイベントをフックとして実装することがほとんどでした。
しかし、頻繁に実行されるスクロールイベントは、ブラウザにとって優しい処理とは言えず、特にスマホではスクロール詰まり(Scroll Jank)を引き起こしてしまうこともありました。
やるんだったら、パフォーマンスも改善したい…。
Intersection Observerとは
その名の通り「Intersection(要素間交差)」を「Observe(監視)」するAPI。
任意の要素(DOM)同士の交差を監視することが出来ます。
デフォルトでviewport(見えている範囲)とある要素が交差=ある要素が見えたら何かする、というものです。
その名の通り「Intersection(要素間交差)」を「Observe(監視)」するAPI。
任意の要素(DOM)同士の交差を監視することが出来ます。
デフォルトでviewport(見えている範囲)とある要素が交差=ある要素が見えたら何かする、というものです。
スクロールイベントをフックするよりパフォーマンスも改善できそうだ
そんなわけで、手始めにIntersection Observerを使って画像の遅延ロードをしてみようというのが今回の趣旨です。
lazyloadの弱点
画像の遅延ロードをさせるには真っ先にlazyloadが思い浮かびました。同じ仕組みでIntersectionObserver対応に書き換えようと思いましたが、
lazyloadにも弱点はあります。
▪️lazyloadの弱点▪️
1)画像のパスをdeta-属性に書かなければならず少し面倒。2)JSがなんらかの理由で動作しなかった場合、画像が一切表示されない。
3)だからインデックスでも隙が。
なので、原理はlazylordと全く同じだけども複数人で運用やってる以上は、組み込みは楽にしたい。
imgタグはそのままでコーディングには影響しない仕組みにしようと思いました。
具体的には、ページが読み込まれる前に全てのimgパスを取得し、deta-属性に代入しつつ同時にダミー画像に一旦置き換え。
最後にlazyloadと同じようにウインドウ内に入ってくる画像から、元の画像に復帰といった具合。
そうするとJSの発火のタイミングが重要になってきます。
読込みのタイミング
よくJSで使うのはwindow.onloadだとかfunctionや$(document).readyだけど、画像のパスを満遍なく拾う関係上、確実にDOM生成された瞬間に発火させたいんです。
調べるとDOMContentLoadedっていうイベントハンドラがあったので最初の一行はこうなります。
1 2 3 |
<script> document.addEventListener("DOMContentLoaded", function(event) { |
画像のパスを取得してダミーの画像に置き換える
ここからは実際のlazyloadの処理を行う前の下準備。画像のパスを取得してダミーの画像に置き換えるまでです。
これをすることでhtmlのコーディングを通常通り行えば良いのでlazyloadの弱点が一つ解消されました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//画像のセレクタ定義 var select = 'img'; var imgselect = document.querySelectorAll(select); //画像のパスを配列に代入 var imgPath = []; //ループ処理 for(var i=0; i<imgselect.length; i++) { //data-src属性の準備 imgselect[i].setAttribute("data-src", "preload"); //data-srcに画像のパスを代入 imgselect[i].dataset.src = imgselect[i].src; //画像のパスをdata-srcに代入終わったらダミー画像に置き換え imgselect[i].src = "assets/images/preload.png"; |
Intersection Observerで交差監視
いよいよここからが本丸。Intersection Observerで要素がウインドウ内に居るか監視をして
ウインドウ内に来たら”画像のURLを元に戻す=画像読み込み”という工程部分です。
こちらを参考にさせていただきました。
https://firstlayout.net/animation-on-elements-entered-on-the-screen/
これで、スクロールイベントもさようならです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
//↓ループ続き// ( () => { // 画像のセレクタの定義を再読み込み const sample = document.querySelectorAll( select ); //監視start const observer = new IntersectionObserver( entries => { //上で定義したセレクタを全て処理 entries.forEach( entry => { //要素が画面内に入って来た場合 if( entry.intersectionRatio > 0 ) { //画像のパスをdata-srcに保存したものに書き換え entry.target.src = entry.target.dataset.src; observer.unobserve( entry.target ); } else { } },{ // optionsを記述(root marginなど) rootMargin: '-200px 0px', }); //監視したい要素毎に監視させる sample.forEach( img => { observer.observe( img ); }); })(); } }); |
注意しなきゃならない点
本記事掲載時点でIntersection observer に対応しているブラウザは、 Chrome ・ Firefox ・Edge です( iOS 版はいずれも未対応)。Can I use… : IntersectionObserver
ただし、対応していないブラウザでも Polyfilを読ませることで動作します。
ソースコード(フル)
役割毎に説明したソースコードを合体させるとこんな風になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
//画像のセレクタ定義 var select = 'img'; var imgselect = document.querySelectorAll(select); //画像のパスを配列に代入 var imgPath = []; //ループ処理 for(var i=0; i<imgselect.length; i++) { //data-src属性の準備 imgselect[i].setAttribute("data-src", "preload"); //data-srcに画像のパスを代入 imgselect[i].dataset.src = imgselect[i].src; //画像のパスをdata-srcに代入終わったらダミー画像に置き換え imgselect[i].src = "assets/images/preload.png"; // options( () => { const sample = document.querySelectorAll( select ); const observer = new IntersectionObserver( entries => { entries.forEach( entry => { if( entry.intersectionRatio > 0 ) { entry.target.src = entry.target.dataset.src; observer.unobserve( entry.target ); } else { } }); }); sample.forEach( img => { observer.observe( img ); }); })(); } }); </script> <script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script> |