プログラミングの役立つ記事をお届けします

[Framework7]バーチャルリストで無限スクロールをする

「HTML5アプリで膨大なリスト表示するんだけど、li要素が増えすぎると動作が重くなる。何かいい方法ないもんかね?」

こんにちは、タカフです。

HTML5でスマホアプリを作っていると結構ありがちな機能なのが、
最初のページ読み込み時は20記事くらい表示しておいて、その後下スクロールする度に新しいli要素を追加していく機能。

一般的にこれを無限スクロール機能といいます。

Yahoo!Japanのスマホ版トップページもそうなっていますよね。

ただ、無限スクロールには一つ問題があって、
無限スクロールといいつつも本当にli要素となるコンテンツが無限にあると、膨大なli要素を追加することでブラウザが重くなっていきます。。

その分メモリを食ってますからね。そりゃそうです。

この問題を解決してくれるのがバーチャルリストという仕組みです。

本記事ではバーチャルリストの紹介をしていきます。

膨大なli要素の描画にはバーチャルリストを使う

バーチャルリストというのはスクロールしていても表示に必要な箇所だけ描画しておく仕組みです。

図でいうと以下のようなイメージです。

css的には、ul要素がheightを保ってくれて、li要素がposition:relative;とtopを使ってスマホ画面の特定位置に配置する仕組みとなっています。

これで1000件くらいのli要素があったとしても、描画しているのは数十件ほどに抑えられるのでメモリ消費を抑えられ動作を軽くすることも出来ます。

Framework7にはこの機能が備わっています。

バーチャルリストのデモ画面

どういう動きかわかった方が早いので先にデモ画面をお見せします。

下記画面でずっと下にスクロールしてみてください。

いくらスクロールしてもこれこそ無限にli要素があってもずっと軽快に表示してくれます。

直接はこちら↓
https://kahoo.blog/data/demo/virtual_list/index.html

しかもこのデモでは、高さの違うli要素も含めることが出来ているのが注目ポイントです

バーチャルリストの実装サンプルコード

それではサンプルコードの紹介です。

Framework7のお作法に従って実装しています。

バーチャルリストを入れるdivは予め空にしておきます。

<template>
  <div class="page" data-name="home">
    <div class="navbar">
      <div class="navbar-bg"></div>
      <div class="navbar-inner">
        <div class="title sliding">バーチャルリスト</div>
      </div>
    </div>

    <div class="page-content">
      <!-- バーチャルリスト -->
      <div class="list virtual-list media-list">
        <!-- 初期は空のままにしておく -->
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    on: {
      pageInit: function (e) {
        // ダミー項目の配列
        var items = [];
        for (var i = 1; i <= 100; i++) {
          items.push({
            title: 'Item ' + i,
            subtitle: (i % 5) ? "通常アイテム" : "ビッグアイテム",
            classname: (i % 5) ? "" : "big-li",
          });
        }

        window.virtualList = app.virtualList.create({
          el: '.virtual-list',  // バーチャルリストのセレクター
          items: items,   // 初回のアイテムデータ
          renderItem: function (item) {
            var html =
              '<li class="' + item.classname + '">' +
              '  <a href="#" class="item-link item-content">' +
              '    <div class="item-inner">' +
              '    <div class="item-title-row">' +
              '    <div class="item-title">' + item.title + '</div>' +
              '    </div>' +
              '    <div class="item-subtitle">' + item.subtitle + '</div>' +
              '    </div>' +
              '  </a>' +
              '</li>';
            return html;
          },
          // cache: false,
          height: function (item) {
            /*
              このheightという名のパラメータに関数を渡して、アイテム一つ一つの高さを返却する。
              これによりVirtualList側が全体の高さを計算してくれてulにそれをセットしてくれて正確なスクロールが出来る。
             */
            if (item.classname == "big-li") {
              return 128;
            } else {
              return 64;
            }
          },
          on: {
            itemsBeforeInsert: (virtualList, fragment) => {

              // reachEndプロパティで最後かどうかの判定が出来る
              if (virtualList.reachEnd ) {

                // 大抵はajaxによるデータ取得だと思うので100msの取得時間シュミレートとする 
                setTimeout(function () {

                  var items = [];
                  for (var i = (virtualList.items.length + 1); i <= (virtualList.items.length + 20); i++) {
                    items.push({
                      title: 'Item ' + i,
                      subtitle: (i % 5) ? "通常アイテム" : "ビッグアイテム",
                      classname: (i % 5) ? "" : "big-li",
                    });
                  }
                  // VirtualListのメソッドを使ってアイテムを追加する
                  virtualList.appendItems(items);
                }, 100);
              }
            }
          }
        });
      }
    }
  }
</script>

li要素挿入前のイベントであるitemsBeforeInsertで、最後のliかどうかをreachEndを見て判断しています。
最後のliだったらデータを取得してappendItemsで追加していってます。
また、ajax取得を演出するために100ms遅らせています。

heightというパラメータに関数を渡して、li要素ごとの高さを返却するのがミソです。

これで高さの違うli要素も含めることが出来ます。

まとめ

Framework7を使えばこういう機能もすぐに実装出来ちゃうのはいいですね。

バーチャルリスト機能を使えばメルカリのようないくらでも商品を軽快に表示するUIの実装も出来そうです。
(その際には画像をすごく軽くする仕組みも必要になってくるかと思います。)

現場からは以上です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です