黒田幸者の秘密基地

無限スクロールの実装

2024/09/24

Category : Web JavaScript


本記事ではJavaScriptによるfetch APIを用いた非同期処理による無限スクロールの実装について解説します。 実際の例のページは以下に記載しています。:こちら

fetch APIとは

HTTPリクエストを行って処理を行う際に使用します。 POSTやGETといったHTTPプロトコルを用いてウェブサーバとやり取りすることができるもので、ゲームの動作や近年注目されているサーバサイドレンダリングといった技術に用いられています。

無限スクロールとは

TwitterやInstagramといったSNS、Youtubeのおすすめ欄などに用いられている、コンテンツが際限なく供給されることでいつまでも下に記事が続いていく形式のサイトを無限スクロールと呼びます。

利点

欠点

以上のような性質上、無限スクロールはモバイル端末のアプリやSNSやショッピングなどのユーザになるべく多く滞在してもらいたい場合などに使用するのが良いとされています。

最近の動向

一昔前には流行しましたが、先述の広告やSEOの問題から近年では下火になってきています。

例えば以前のGoogleの検索結果は無限スクロール形式で表示されていましたが、最近は下部に数字のボタンを複数用意し、ページ遷移する方向にシフトしています。 現在でもモバイル端末の検索結果は無限スクロールですが、PC版ではページ遷移になってきています。

実装例

今回はHTMLとJavaScriptをフロントサイドとし、バックエンドにはRubyを用いて実行しました。

以下は実際の無限スクロールの例で、実際のページはこちらから見ることができます。

HTMLファイルは長いのでここでは載せません。コマンドラインで

curl https://www.big-a-k.com/example

を実行するか、ブラウザでctrl+shift+iでデベロッパーツールを開いて確認してみてください。

実際にHTMLに付与したJavascriptは以下のとおりです。

これはページがスクロールされて画面の下部に近づいたときに、新しいリンクを自動的に読み込むように実装しています。

let page = 1;
let loading = false;
const limit = 20;

function loadLinks() {
    if (loading) return;
    loading = true;
    document.getElementById('loading').style.display = 'block';
    const url = `/api/contents?page=${page}&limit=${limit}`;
// ここでfetchを呼び出している。サーバにはGETオプションでアクセスが投げられる。
    fetch(url)
        .then(response => response.json())
        .then(data => {
            const linksContainer = document.getElementById('items');
            data.forEach(link => {
                const postGrid = document.createElement('div');
                postGrid.className = 'post-index';

                const postCard = document.createElement('div');
                postCard.className = 'post-card';

                const a = document.createElement('a');
                a.href = link;

                const p = document.createElement('h3');
                p.textContent = link;

                const hr = document.createElement('hr');

                a.appendChild(p);
                a.appendChild(hr);
                postCard.appendChild(a);
                postGrid.appendChild(postCard);
                linksContainer.appendChild(postGrid);
            });
            page++;
            loading = false;
            document.getElementById('loading').style.display = 'none';
        })
        .catch(error => {
            console.error('Error loading links:', error);
            loading = false;
            document.getElementById('loading').textContent = '読み込みに失敗しました';
        });
}
function checkAndLoad() {
    const nearBottom = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 200;
    if (nearBottom && !loading) {
        loadLinks();
    }
}
function onScroll() {
    checkAndLoad();
}
window.addEventListener('scroll', onScroll);
// 初期ロード
window.addEventListener('load', loadLinks);
// スクロールチェックを初期化する
setTimeout(checkAndLoad, 1000);

そしてアプリケーションサーバにおけるRubyのコードは以下の通りです。

例えばfetchで/api//contents?page=2&limit=20を要求された時、2ページ目から20個ずつコンテンツを返すという機能を実装しています。

require 'sinatra'
require 'json'
# 仮のリンクデータ
links = (1..10000).map { |i| "https://example.com/page#{i}" }
get '/contents' do
  content_type :json
  uid = params['page']
  page = params['page'].to_i || 1
  limit = params['limit'].to_i || 10
  start = (page - 1) * limit
  data = links[start, limit]
  data.to_json
end

またリクエストをアプリケーションサーバに投げるためにプロキシを噛ませるのが楽です。例えば具体的にnginxでは

location /api{↲
    rewrite ^/api/(.*)$ /$1 break; # /api/ を削除してプロキシするための書き換え
    proxy_pass http://localhost:50000;  # リクエストをプロキシする先
    proxy_set_header Host $host;        # ホスト情報を転送
    proxy_set_header X-Real-IP $remote_addr; # クライアントのIPアドレスを転送
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # プロキシチェーンのIP情報を転送
    proxy_set_header X-Forwarded-Proto $scheme; # リクエストのプロトコル(HTTP/HTTPS)を転送
}

の様に記述すれば良いでしょう。

参考文献

以下実際の無限スクロールです。

Loading...