4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTML無限スクロール

Last updated at Posted at 2024-01-06

HTML無限スクロール

ブラウザでページ遷移ではなくスクロールで次々とデータロードして続きを見せるページのサンプルを作成しました

1.仕組みとしては前(又は次)のデータを読み込んで新しいHTML要素を追加する
2.現在のスクロール位置から前後5000ピクセル以上離れた要素を削除する

端に到達するたびに 1~2 を繰り返し無限にスクロールしていきます
特定範囲外のHTML要素は削除されるためメモリ消費量が一方的に増加しないのでメモリ消費を抑えられます
超高解像度なモニター使ってる方はサンプルが上手く動かないかもしれません

サンプル動作はこちら
https://ikuo0.github.io/infinite-scroll/infinite_scroll.html

サンプルソース

HTML

https://github.com/ikuo0/infinite-scroll/blob/main/infinite_scroll.html

Javascript

https://github.com/ikuo0/infinite-scroll/blob/main/infinite_scroll.js

要所解説

データロード部

指定された数だけ配列を設定して返しているだけですが、WEBAPIを想定するため Proimse で実装しています

    self.loadData = function(startIndex, size) {
        var me = this;
        return new Promise(function(resolve) {
            var value = [];
            var index = [];
            var endIndex = startIndex + size;
            for(var i = startIndex; i < endIndex; i += 1) {
                value.push("### " + String(i) + " ###");
                index.push(i);
            }
            resolve({
                "size": value.length,
                "value": value,
                "index": index,
            });
        });
    }

拡張部

上スクロール時の拡張ロジックについて解説します

1.section 作成、既存のsection

データロード1回につき1つsectionが増え、その要素に取得したデータが反映されます
データロードを3回実行したら3つの section が増え、各々に数十件なり数百件のデータが反映された状態となります

2.最上段の要素を取得

querySelectorAll の配列順番が座標の昇順であるか不明なので念のため全て列挙してから最小値を確認し、最上段の要素を取得します

3.前のデータを取得

一番若いインデックスを取得しデータロード時のキーとします
「2」で取得した最上段の要素の子要素の最上段を取得しインデックスを取得

4.要素の追加とスクロール位置修正

loadData関数でデータを読み込みHTML要素を before で追加します
beforeで要素追加を行うとスクロール位置がズレるため元の位置を算出して、その位置にスクロールします(戻します)

    // onScroll 抜粋
        if(scrollY < 50) {
            // section 最上段の要素を取得
            var sections = document.querySelectorAll('[name="section"]');

            // sections[0] でも良いが座標順との保証も無いので念のため座標最小値を参照先とする
            var sectionsTopBottom = me.setupTopsAndBottoms(sections);
            var sectionTopIndex = n1d.argmin(sectionsTopBottom.top);
            var sectionTop = sections[sectionTopIndex];

            // section の子要素の最上段を取得
            var items = sectionTop.querySelectorAll('[name="section-item"]');
            var itemsTopBottom = me.setupTopsAndBottoms(items);

            // sections の時同様、最上段の要素を確実に取得する
            var itemTopIndex = n1d.argmin(itemsTopBottom.top);
            var topItem = items[itemTopIndex];
            // 前のデータを読むためにインデックスを取得
            var index = parseInt(topItem.getAttribute("item-index"));

            // データ読み込み
            me.loadData(index - me.constDataSize, me.constDataSize)
            .then(function(data) {
                // 新しい section を作成、データを反映
                var section = me.createNewSection(data.size, data.value, data.index);
                sectionTop.before(section);
                onReady(function() {// 要素の描画が終わったら
                    // スクロール位置を元に戻す
                    window.scrollTo(0, section.clientHeight + scrollY);
                });
            });
        }

範囲外の削除

現在のスクロール位置から前後5000ピクセルを超えた位置にあるHTML要素を削除します
上下範囲の当たり判定を取得、当たっていない要素を全て列挙し remove していきます

    self.deleteOutRangeView = function() {
        var me = this;
        var scrollY = window.scrollY;
        var innerHeight = window.innerHeight;
        var margin = me.constVisibleMargin;
        var topEdge = scrollY - margin;
        var bottomEdge = topEdge + innerHeight + margin * 2;
        var sections = document.querySelectorAll('[name="section"]');
        var sectionsTopBottom = me.setupTopsAndBottoms(sections);
        var flags = [];
        for(var i = 0; i < sectionsTopBottom.size; i += 1) {
            var top = sectionsTopBottom.top[i];
            var bottom = sectionsTopBottom.bottom[i];
            if(
                (top < topEdge && bottom < topEdge) ||
                (top > bottomEdge && bottom > bottomEdge)
                ) {
                flags.push(1);
            } else {
                flags.push(0);
            }
        }
        for(var i = 0; i < flags.length; i += 1) {
            if(flags[i] != 0) {
                sections[i].remove();
            }
        }
    }

注意点

サンプル動かしたり、実装見ていれば分かるとは思うのですが
下限・上限不明の状態でスクロールしているため
スクロールバーの位置 = 実際のデータインデックス
とはなりません
対象が10万件のデータ群だとして最下段までスクロールしたら10万件目が見えるのか? となった場合に当然見れません
300件単位でスクロールさせていたら次の300件目以降が見れるようになるだけです

想定用途

SNSのような非業務系になると思います、ページ遷移が数字でしっかり見れないですし先頭・末尾へジャンプできませんので、このままでは業務系には向かないと思います
ページジャンプ機能も複合して付けることでなんとかなるかも?

document.querySelectorAll の列挙順番について

画面表示上の 左から右に、上から下に の順番に配列に収まっているようなので
最上段がほしければ0番目の要素を elements[0] で取得すればいいんだろうなと思ったのですが
公式ドキュメント見ても座標順に配列作るとはどこにも書いてなかったので手間ですが座標確認して最上段の物を検索する実装をしています

以上です

4
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?