Vue.jsでTwitter的なタイムラインを実現するために仮想スクロールのライブラリを探していたところ、vue-virtual-scrollerがピッタリだったので、ソースとか挙動を検証した結果を書きます。
https://qiita.com/miyaoka/items/5183c143e195455ae65e#9-virtual-scrolling で紹介されています。
仮想スクロールとは
大量データを一覧表示する際、全件をDOMツリーに展開するとメモリを使いすぎて重たくなり、最悪クラッシュします。(特にスマホ)
そこで、可視領域(viewport)とその前後のみDOMを構築し、スクロールするたびに可視領域orその前後かを判別して、対象であればDOMを構築、対象外であれば破棄といったことを行い、DOMノードの数をある程度一定にします。
また、単純にDOMの数を一定にすると、各行の高さが異なる場合に再描画するとスクロール位置が変わってカクついたり、一気に一番下までスクロールができなくなるので、すべて表示していると仮定して一覧の高さを表現します。
例えば、実際に画面表示していない領域は padding-top
と padding-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プロパティに変更があった場合、高さを再計算する)、 watchData
を true
にしてitemのプロパティ変更時に常に高さを再計算する方法があるようです。
無限スクロール
いわゆる無限スクロール(初回○件、一番下まで到達するとさらに○件読み込む・・を延々と繰り返す)に対応していないライブラリが結構多いのですが、vue-virtual-scrollerは対応できていました。
バンバン無限スクロールしましょう!!
スクロール対象
vue-virtual-scrollerはデフォルトだと、RecycleScroller/DynamicScroller配下に overflow-y: scroll
を付与して、スクロールイベントを監視します。
そのため、画面全体(window
)や、Onsen UIなどのようにページ単位(v-ons-page
)でスクロールさせたい場合は、 page-mode
を true
にする必要があります。
なお、page-mode
を false
にする場合は、RecycleScroller/DynamicScrollerに高さを設定する必要があります。
Note: 内部的には、親のノードを遡っていき、最初に現れたスクロール可能な要素(なければ、 window
)に対してスクロールイベントを監視する実装になっていました。
まとめ
これらの要件を満たすことで、パフォーマンスを維持したままTwitterのように複雑な一覧のUIでも実装できます。
仮想スクロールのライブラリは沢山ありますが、ここまでかゆいところに手が届くライブラリはあまりないのでおすすめです!!
vue-virtual-scrollerのリポジトリはこちら
GitHub - Akryum/vue-virtual-scroller: ⚡️ Blazing fast scrolling for any amount of data