概要: このチュートリアルでは、JavaScriptの無限スクロール機能を実装する方法を学びます。
構築するもの
次の図は、構築するWebアプリケーションを示しています。

ページには、APIから取得した引用符のリストが表示されます。デフォルトでは、10個の引用符が表示されます。
ページの一番下までスクロールすると、Webアプリケーションはローディングインジケーターを表示します。さらに、APIを呼び出してより多くの引用符を取得し、現在のリストに追加します。
使用するAPIのURLは次のとおりです。
https://api.javascripttutorial.net/v1/quotes/?page=1&limit=10
Code language: JavaScript (javascript)
APIは、page
とlimit
の2つのクエリ文字列を受け入れます。これらのクエリ文字列を使用すると、サーバーから引用符をページ分割できます。
引用符は、page
クエリ文字列によって決定されるページに分割されます。各ページには、limit
パラメーターで指定された数の引用符があります。

ここをクリックして、JavaScriptの無限スクロール機能を使用する最終的なWebアプリケーションをご覧ください。
プロジェクト構造を作成する
まず、infinite-scroll
という新しいフォルダーを作成します。そのフォルダー内に、css
とjs
の2つのサブフォルダーを作成します。
次に、css
フォルダーにstyle.css
を、js
フォルダーにapp.js
を作成します。
3番目に、infinite-scroll
フォルダーに新しいHTMLファイルindex.htmlを作成します。
最終的なプロジェクトフォルダー構造は次のようになります。

index.htmlファイルにコードを追加する
index.html
を開き、次のコードを追加します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Infinite Scroll - Quotes</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<h1>Programming Quotes</h1>
<div class="quotes">
</div>
<div class="loader">
<div></div>
<div></div>
<div></div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>
Code language: HTML, XML (xml)
index.html
ファイルで、style.css
をheadセクションに、app.js
をbodyセクションに配置します。
bodyセクションには、クラス名container
を持つdiv
があります。container要素には4つの子要素があります。
- ページ見出しを表示する見出し1(h1)。
- すべての引用符の親要素になるクラス
quotes
を持つdiv
。 - ローディングインジケーターを表示するローダー。デフォルトでは、ローディングインジケーターは非表示です。
app.jsを作成する
以下では、querySelector()
を使用して、クラスquotes
を持つdiv
とloader
を選択します。
const quotesEl = document.querySelector('.quotes');
const loader = document.querySelector('.loader');
Code language: JavaScript (javascript)
getQuotes()関数
次のgetQuotes()
関数は、APIを呼び出して引用符を返します。
const getQuotes = async (page, limit) => {
const API_URL = `https://api.javascripttutorial.net/v1/quotes/?page=${page}&limit=${limit}`;
const response = await fetch(API_URL);
// handle 404
if (!response.ok) {
throw new Error(`An error occurred: ${response.status}`);
}
return await response.json();
}
Code language: JavaScript (javascript)
getQuotes()
関数は、page
とlimit
の2つの引数を受け入れます。 Fetch APIを使用して、APIからデータを取得します。
fetch()
はpromiseを返すため、await
構文を使用してレスポンスを取得できます。そして、レスポンスオブジェクトのjson()
メソッドを呼び出してjsonデータを取得します。
getQuotes()
は、JSONデータに解決されるpromiseを返します。
getQuotes()
関数はawait
キーワードを使用するため、async
関数である必要があります。
showQuotes()関数
以下は、quotes
配列から<blockquote>
要素を生成し、それらをquotes
要素に追加するshowQuotes()
関数を定義します。
// show the quotes
const showQuotes = (quotes) => {
quotes.forEach(quote => {
const quoteEl = document.createElement('blockquote');
quoteEl.classList.add('quote');
quoteEl.innerHTML = `
<span>${quote.id})</span>
${quote.quote}
<footer>${quote.author}</footer>
`;
quotesEl.appendChild(quoteEl);
});
};
Code language: JavaScript (javascript)
仕組み
showQuotes()
は、forEach()
メソッドを使用して、quotes
配列を反復処理します。
各引用符オブジェクトに対して、quote
クラスを持つ<blockquote>
要素を作成します。
<blockquote class="quote">
</blockquote>
Code language: HTML, XML (xml)
そして、テンプレートリテラル構文を使用して、引用符オブジェクトのHTML表現を生成します。 HTMLを<blockquote>
要素に追加します。
以下に、生成された<blockquote>
要素の例を示します。
<blockquote class="quote">
<span>1)</span>
Talk is cheap. Show me the code.
<footer>Linus Torvalds</footer>
</blockquote>
Code language: HTML, XML (xml)
各反復処理の最後に、関数はappendChild()
メソッドを使用して、<blockquote>
要素をquotesEl
要素の子要素に追加します。
ローディングインジケーターの表示/非表示関数
以下は、ローディングインジケーター要素を表示および非表示にする2つの関数を定義します。
const hideLoader = () => {
loader.classList.remove('show');
};
const showLoader = () => {
loader.classList.add('show');
};
Code language: JavaScript (javascript)
ローディングインジケーターの不透明度は0であり、デフォルトでは非表示です。 .show
クラスはローディングインジケーターの不透明度を1に設定し、表示にします。
ローディングインジケーターを非表示にするには、ローディングインジケーター要素からshow
クラスを削除します。同様に、ローディングインジケーターを表示するには、そのクラスリストにshow
クラスを追加します。
制御変数を定義する
以下では、currentPage
変数を宣言し、1に初期化します。
let currentPage = 1;
Code language: JavaScript (javascript)
ページの一番下までスクロールすると、アプリケーションは次の引用符を取得するためにAPIリクエストを行います。その前に、currentPage
変数を1ずつ増やす必要があります。
一度に取得する引用符の数を指定するには、次のような定数を使用できます。
const limit = 10;
Code language: JavaScript (javascript)
次のtotal
変数には、APIから返された引用符の合計が格納されます。
let total = 0;
Code language: JavaScript (javascript)
hasMoreQuotes()関数
次のhasMoreQuotes()
関数は、次の場合はtrue
を返します。
- 最初の取得(
total === 0
)の場合 - または、APIから取得する引用符がさらにある場合(
startIndex
<total
)
const hasMoreQuotes = (page, limit, total) => {
const startIndex = (page - 1) * limit + 1;
return total === 0 || startIndex < total;
};
Code language: JavaScript (javascript)
loadQuotes()関数
以下は、4つのアクションを実行する関数を定義します。
- ローディングインジケーターを表示します。
- 取得する引用符がさらにある場合は、
getQuotes()
関数を呼び出してAPIから引用符を取得します。 - ページに引用符を表示します。
- ローディングインジケーターを非表示にします。
// load quotes
const loadQuotes = async (page, limit) => {
// show the loader
showLoader();
try {
// if having more quotes to fetch
if (hasMoreQuotes(page, limit, total)) {
// call the API to get quotes
const response = await getQuotes(page, limit);
// show quotes
showQuotes(response.data);
// update the total
total = response.total;
}
} catch (error) {
console.log(error.message);
} finally {
hideLoader();
}
};
Code language: JavaScript (javascript)
getQuotes()
関数が非常に高速に実行されると、ローディングインジケーターが表示されません。
ローディングインジケーターが常に表示されるようにするには、setTimeout()
関数を使用できます。
// load quotes
const loadQuotes = async (page, limit) => {
// show the loader
showLoader();
// 0.5 second later
setTimeout(async () => {
try {
// if having more quotes to fetch
if (hasMoreQuotes(page, limit, total)) {
// call the API to get quotes
const response = await getQuotes(page, limit);
// show quotes
showQuotes(response.data);
// update the total
total = response.total;
}
} catch (error) {
console.log(error.message);
} finally {
hideLoader();
}
}, 500);
};
Code language: JavaScript (javascript)
setTimeout()
関数を追加することにより、ローディングインジケーターは少なくとも0.5秒間表示されます。また、setTimeout()
関数の2番目の引数を変更することで、遅延を微調整できます。
スクロールイベントをアタッチする
ユーザーがページの一番下までスクロールしたときにより多くの引用符をロードするには、スクロールイベントハンドラーをアタッチする必要があります。
スクロールイベントハンドラーは、次の条件が満たされている場合、loadQuotes()
関数を呼び出します。
- まず、スクロール位置がページの一番下にある場合。
- 次に、取得する引用符がさらにある場合。
スクロールイベントハンドラーは、次の引用符をロードする前に、currentPage
変数も増やします。
window.addEventListener('scroll', () => {
const {
scrollTop,
scrollHeight,
clientHeight
} = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 5 &&
hasMoreQuotes(currentPage, limit, total)) {
currentPage++;
loadQuotes(currentPage, limit);
}
}, {
passive: true
});
Code language: JavaScript (javascript)
ページを初期化する
ページが最初にロードされるときに、最初の引用符のバッチをロードするためにloadQuotes()
関数を呼び出す必要があります。
loadQuotes(currentPage, limit);
app.jsコードをIIFEでラップする
定義した変数や関数の競合を避けるために、app.js
ファイル内のコード全体をIIFEでラップできます。
最終的なapp.js
は次のようになります。
(function () {
const quotesEl = document.querySelector('.quotes');
const loaderEl = document.querySelector('.loader');
// get the quotes from API
const getQuotes = async (page, limit) => {
const API_URL = `https://api.javascripttutorial.net/v1/quotes/?page=${page}&limit=${limit}`;
const response = await fetch(API_URL);
// handle 404
if (!response.ok) {
throw new Error(`An error occurred: ${response.status}`);
}
return await response.json();
}
// show the quotes
const showQuotes = (quotes) => {
quotes.forEach(quote => {
const quoteEl = document.createElement('blockquote');
quoteEl.classList.add('quote');
quoteEl.innerHTML = `
<span>${quote.id})</span>
${quote.quote}
<footer>${quote.author}</footer>
`;
quotesEl.appendChild(quoteEl);
});
};
const hideLoader = () => {
loaderEl.classList.remove('show');
};
const showLoader = () => {
loaderEl.classList.add('show');
};
const hasMoreQuotes = (page, limit, total) => {
const startIndex = (page - 1) * limit + 1;
return total === 0 || startIndex < total;
};
// load quotes
const loadQuotes = async (page, limit) => {
// show the loader
showLoader();
// 0.5 second later
setTimeout(async () => {
try {
// if having more quotes to fetch
if (hasMoreQuotes(page, limit, total)) {
// call the API to get quotes
const response = await getQuotes(page, limit);
// show quotes
showQuotes(response.data);
// update the total
total = response.total;
}
} catch (error) {
console.log(error.message);
} finally {
hideLoader();
}
}, 500);
};
// control variables
let currentPage = 1;
const limit = 10;
let total = 0;
window.addEventListener('scroll', () => {
const {
scrollTop,
scrollHeight,
clientHeight
} = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 5 &&
hasMoreQuotes(currentPage, limit, total)) {
currentPage++;
loadQuotes(currentPage, limit);
}
}, {
passive: true
});
// initialize
loadQuotes(currentPage, limit);
})();
Code language: JavaScript (javascript)
これは、Webアプリケーションの最終バージョンです。