無限スクロールの実装
2024/09/24
Category : Web JavaScript
本記事ではJavaScriptによるfetch APIを用いた非同期処理による無限スクロールの実装について解説します。 実際の例のページは以下に記載しています。:こちら
fetch APIとは
HTTPリクエストを行って処理を行う際に使用します。 POSTやGETといったHTTPプロトコルを用いてウェブサーバとやり取りすることができるもので、ゲームの動作や近年注目されているサーバサイドレンダリングといった技術に用いられています。
無限スクロールとは
TwitterやInstagramといったSNS、Youtubeのおすすめ欄などに用いられている、コンテンツが際限なく供給されることでいつまでも下に記事が続いていく形式のサイトを無限スクロールと呼びます。
利点
- モバイル端末でのユーザービリティの向上
- ボタンを押すという操作の削減
- ユーザの意思決定の回数は限りがある。何も考えずに操作できるのは利点。
欠点
- 再現性の欠如
- デバッグが大変
- 同一コンテンツの再発見が難しい
- フッターにアクセスできない
- SEOの成績悪化
- 広告の表示が困難
- 広告はページ来訪時に読み込まれるので、無限スクロールでは広告の表示に工夫を施す用意がある。
以上のような性質上、無限スクロールはモバイル端末のアプリや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個ずつコンテンツを返すという機能を実装しています。
- Ruby
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)を転送
}
の様に記述すれば良いでしょう。
参考文献
- Mmdn web docs “フェッチAPIの使用”
- U-Site “無限スクロール:利用すべきとき、避けるべきとき”
以下実際の無限スクロールです。
Loading...