ニワトリのたまご

宇宙とソフトウェア開発

v-virutal-scrollの初期化が重いのでv-intersectで無限スクロールを実装した

はじめに

Vuetifyのv-virtual-scrollを使ってリストのコンポーネントを作成しています。データ数が多くなってもスクロールは軽いままで良いと思っていたのですが、どうも最初に表示するのに時間がかかる。調べてみるとデータの処理はすぐに終わってリストの部分にデータを渡せているのですが、リストのコンポーネント自体の初期化に時間がかかっているようでした。

そこで、一度に全てのデータを初期表示するのではなくスクロールのたびにデータを読み込むために、v-intersectを使うことにしました。

環境

vue: 3.4.27
vuetify: 3.6.8

結論

v-intersectを使うことで、初期表示時に描画が遅い問題は解消しました。

実装例

vueの部分はこんな感じ。

<v-virtual-scroll :height="50" :items="items">
  <template v-slot:default="{ item, index }">
    <span v-if="index === items.length - 1" v-intersect.once="onIntersect"></span>
    <v-list-item>{{ item.name }}</v-list-item>
  </template>
</v-virtual-scroll>

データの最下部になったら要素を追加して、その要素に一度だけintersectの判定をするところがポイントです。 (参考のところの実装をほぼ流用)

intersectした場合の処理はこんな感じ。

const LIMIT = 200;
let current_page = 0;
function onIntersect(_entries: IntersectionObserverEntry[], _observer: IntersectionObserver, isIntersecting: boolean) {
  if (isIntersecting) {
    let lastItemNumber = LIMIT * (current_page + 1);
    if (lastItemNumber > srcItems.value.length) {
      lastItemNumber = srcItems.value.length;
    } else {
      current_page += 1;
    }
    viewItems.value = srcItems.value.slice(0, lastItemNumber);
  }
}

引数の三番目にintersectしているかのフラグが渡されるのでそれを処理の契機に使います。 データの最後に行ったらページをカウントアップしないようにします。 sliceは特にデータの範囲外を指定してもエラーにはならないのですが、気持ち悪いので存在する範囲のデータに揃えるようにしています。

まとめ

v-intersectを使うことで初期表示が重くなる問題は解消しました。 v-intersectの追加実装はそこまでコード量も多くないので処理高速化のための選択肢になりやすいかなと思いました。 ただ、v-virtual-scrollは表示範囲以外の描画はしないはずなので、コンポーネントの初期化に時間がかかってしまうのがちょっと謎です。

参考

note.shiftinc.jp

ebato-tech-blog.com

qiita.com