🤾‍♂️

スクロールで画面に表示されるまで処理を遅らせる方法

2022/06/01に公開

今回紹介する内容

コンテンツ数が多く読み込みに時間がかかる場合できるだけ読み込みを遅らせて全体のパフォーマンスを向上させる 遅延ロード という手法があります。今回はRSSのロードを題材に遅延ロードの実装方法についてご紹介します。なお、この手法はコンテンツのロード以外にも「重い処理で開始を遅らせても良い」場合にも有効です。

やりたいこと

遅延ロードしたい。

あらすじ

処理は大きく分けて3つの処理に別れます

  1. コンテンツの枠組みは事前に作成
  2. コンテンツの画面表示を監視
  3. コンテンツが表示された ⇨ コンテンツを処理を開始(ロード)する

サンプル・ソース

全体のソースはGitHubに公開しています。

https://github.com/Harurow/zenn-sample-lazy-load

動作イメージこんな感じです。青いヘッダ部の下のコンテンツ領域を縦にスクロールしてみてください。実際のRSSの記事は読み込んでいませんが擬似的に読み込みに3秒かかると仮定して動作させています。

準備、HTMLで外枠だけ作る

今回はHTML, CSSではレイアウトのみで何も仕掛けはありません。
本筋は次のJavaScriptで行います。

index.html
... 略 ...
<body>
  <header>
    遅延ロードサンプル
  </header>
  <main>
    <ul class="content-root"> 
      <!-- ここに遅延ロードするコンテンツをいっぱいJavaScriptで追加します  -->
      <!-- Gridになっていて表示幅に合わせていコンテンツを折り返します  -->
    </ul>
  </main>
</body>
<script src="./RssFeed.js"></script> <!-- コンテンツ(RSS)を表すクラス -->
<script src="./main.js"></script> <!-- 処理本体 -->
... 略 ...

JavaScriptでコンテンツ追加、監視、ロード

処理は先ほど大きく3つ分かれていると紹介しましたが実際の実装は入り乱れています。

処理の全体を紹介する前に、先にコンテンツを表すクラス RssFeed に触れておきます。
関数を2つ持ちRSSコンテンツそのものを表します。

  • createTag コンテンツの枠組みを作ります。jQueryで作って戻り値で返します。
  • load 実際の重い処理。コンテンツのロードを行います。
RssFeed.js
class RssFeed {
  createTag() // コンテンツの枠組みのタグを構築します
  load() // コンテンツを実際にロードします
}

細かい手順

  1. コンテンツを監視するオブジェクト IntersectionObserver を変数observerとして生成
main.js
  // 監視オブジェクトを作成
  const observer = new IntersectionObserver((entries) => {
    // ここで表示されたコンテンツの処理を実装する。あとからまた説明します。
  })
  1. コンテンツの枠組み全て作る処理。今回は配列topicsforEachで回して実装
main.js
+ // トピックの配列。RSSの記事を取得するためのトピック
+ const topics = ['javascript', 'python', 'aws', 'typescript', 'react',
+ ......
+               'raspberrypi', 'sql', 'nuxtjs', 'npm'
+ ]

  // 監視オブジェクトを作成
  const observer = new IntersectionObserver((entries) => {
    // ここで表示されたコンテンツの処理を実装する。あとからまた説明します。
  })

+ // 記事を保持するルート要素
+ const jContentRoot = $('.content-root')
+
+ // トピック分RSS記事を作成する
+ topics.forEach((topic, no) => {
+   const url = `https://zenn.dev/topics/${topic}/feed`
+   const feed = new RssFeed(topic, url)
+ 
+   const jFeedTag = feed.createTag()
+       .appendTo(jContentRoot)   // コンテンツルートに追加
+ })
  1. コンテンツを監視するように修正。また、表示時の処理でコンテンツがどれか判断できるようにマーキングする。
main.js
  // トピックの配列。RSSの記事を取得するためのトピック
  const topics = ['javascript', 'python', 'aws', 'typescript', 'react',
  ......
                'raspberrypi', 'sql', 'nuxtjs', 'npm'
  ]

+ /**
+  * フィードを保持する配列
+  * @type {RssFeed[]}
+  */
+ const feeds = []

  // 監視オブジェクトを作成
  const observer = new IntersectionObserver((entries) => {
    // ここで表示されたコンテンツの処理を実装する。あとからまた説明します。
  })

  // 記事を保持するルート要素
  const jContentRoot = $('.content-root')

  // トピック分RSS記事を作成する
  topics.forEach((topic, no) => {
    const url = `https://zenn.dev/topics/${topic}/feed`
    const feed = new RssFeed(topic, url)
  
    const jFeedTag = feed.createTag()
+       .attr('data-feed-no', no) // 番号で逆引きできるように属性に配列番号を記録
        .appendTo(jContentRoot)   // コンテンツルートに追加

    // フィードを格納しておく
+   feeds.push(feed)
+
+   // 監視対象へ
+   observer.observe(jFeedTag[0])
  })

ここまでで監視コンテンツの表示の監視ができるようになりました。

  1. 最後にコンテンツ表示されたときにロードされる処理を追加します。
main.js
  ......
  // 監視オブジェクトを作成
  const observer = new IntersectionObserver((entries) => {
-   // ここで表示されたコンテンツの処理を実装する。あとからまた説明します。
+   entries.forEach((entry) => {
+     if (entry.isIntersecting) {
+       // 表示された場合はRSSをロードして監視対象外とする
+       const elm = entry.target  // コンテンツを取得
+       const jElm = $(elm)       // jQueryオブジェクト化
+       const feedNo = jElm.attr('data-feed-no') // 属性data-feed-noから番号を取得
+
+       // RssFeedのロードを呼び出す
+       feeds[feedNo].load()  // 作成時に保管した配列からインスタンスを取得しロード処理を行う
+
+       // 監視の対象から外す
+       observer.unobserve(elm)
+     }
+   })
  })

  ......

以上で遅延ロードの完成です。

最後に

今回はRSSの遅延ロードを題材にご紹介いたしました。私作ってるChrome拡張のRSS-ReaderでもRSSの一覧を表示するところでこのテクニックを利用しています。
ご紹介したサンプルソースでもZennのトピックスを対象にしてRSSを読み込みたかったのですが、実際のところはCORSで読み込めないので今回は雰囲気掴めるように擬似的にロードしたような感じにして紹介しています。

今回の題材元としたRSS-Readerの紹介記事です

是非読んでみて気に入ったら使ってみてください。

https://zenn.dev/harurow/articles/812dabf395797f

Discussion