Vue.jsの仮想スクロールはvue-virtual-scrollerを使うと良さそう


Vue.jsでTwitter的なタイムラインを実現するために仮想スクロールのライブラリを探していたところ、vue-virtual-scrollerがピッタリだったので、ソースとか挙動を検証した結果を書きます。

https://qiita.com/miyaoka/items/5183c143e195455ae65e#9-virtual-scrolling で紹介されています。

仮想スクロールとは

大量データを一覧表示する際、全件をDOMツリーに展開するとメモリを使いすぎて重たくなり、最悪クラッシュします。(特にスマホ)
そこで、可視領域(viewport)とその前後のみDOMを構築し、スクロールするたびに可視領域orその前後かを判別して、対象であればDOMを構築、対象外であれば破棄といったことを行い、DOMノードの数をある程度一定にします。

また、単純にDOMの数を一定にすると、各行の高さが異なる場合に再描画するとスクロール位置が変わってカクついたり、一気に一番下までスクロールができなくなるので、すべて表示していると仮定して一覧の高さを表現します。
例えば、実際に画面表示していない領域は padding-toppadding-bottom で余白を設ける実装などがあります。(Twitter Liteとか)

Note: vue-virtual-scrollerでスクロール中の様子。要素が一定以上増えていないことが分かります。vue-virtual-scrollerは全体の高さを min-height に設定して、各行は transform: translateY(XXpx)で表示位置を調整する実装になっています。

各行の高さが動的に変わる場合

各行の高さが固定であれば実装が簡単そうですが、各アイテムの高さがコンテンツによって異なる場合は結構厄介です。
一般的には、描画するまでは仮の高さとしておき、一度描画した後に実際の高さを取得して、その高さをIDなどをキーにしてキャッシュしておくといった実装が多いです。

<template>
  <v-ons-page>
    <DynamicScroller
            :items="items"
            :min-item-size="100"
            :page-mode="true">
      <template v-slot="{ item, index, active }">
        <DynamicScrollerItem
                :item="item"
                :active="active"
                :data-index="index">
          <div
                  :style="{ height: item.height + 'px' }"
                  style="border-bottom: 1px solid black;">
            {{ item.name }}
          </div>
        </DynamicScrollerItem>
      </template>
    </DynamicScroller>
  </v-ons-page>
</template>

↓DynamicScrollerのstate

Note: vue-virtual-scrollerでは、各行のキー(ID)と高さをDynamicScrollerの vscrollData.sizes に保持しているようです。

高さのキャッシュを更新したい場合

キャッシュした高さは以降更新しないので、同じ行のデータが変わらない場合は良いですが、変わることがあれば考慮が必要です。
試してはいないですが、DynamicScrollerの sizeDependencies に高さに影響を与えるitemプロパティを設定するか(指定したitemプロパティに変更があった場合、高さを再計算する)、 watchDatatrue にしてitemのプロパティ変更時に常に高さを再計算する方法があるようです。

無限スクロール

いわゆる無限スクロール(初回○件、一番下まで到達するとさらに○件読み込む・・を延々と繰り返す)に対応していないライブラリが結構多いのですが、vue-virtual-scrollerは対応できていました。
バンバン無限スクロールしましょう!!

スクロール対象

vue-virtual-scrollerはデフォルトだと、RecycleScroller/DynamicScroller配下に overflow-y: scroll を付与して、スクロールイベントを監視します。
そのため、画面全体(window)や、Onsen UIなどのようにページ単位(v-ons-page)でスクロールさせたい場合は、 page-modetrue にする必要があります。

なお、page-modefalse にする場合は、RecycleScroller/DynamicScrollerに高さを設定する必要があります。

Note: 内部的には、親のノードを遡っていき、最初に現れたスクロール可能な要素(なければ、 window )に対してスクロールイベントを監視する実装になっていました。

まとめ

これらの要件を満たすことで、パフォーマンスを維持したままTwitterのように複雑な一覧のUIでも実装できます。
仮想スクロールのライブラリは沢山ありますが、ここまでかゆいところに手が届くライブラリはあまりないのでおすすめです!!

vue-virtual-scrollerのリポジトリはこちら

GitHub - Akryum/vue-virtual-scroller: ⚡️ Blazing fast scrolling for any amount of data