JavaScript無限スクロール

概要: このチュートリアルでは、JavaScriptの無限スクロール機能を実装する方法を学びます。

構築するもの

次の図は、構築するWebアプリケーションを示しています。

JavaScript Infinite Scroll Application

ページには、APIから取得した引用符のリストが表示されます。デフォルトでは、10個の引用符が表示されます。

ページの一番下までスクロールすると、Webアプリケーションはローディングインジケーターを表示します。さらに、APIを呼び出してより多くの引用符を取得し、現在のリストに追加します。

使用するAPIのURLは次のとおりです。

https://api.javascripttutorial.net/v1/quotes/?page=1&limit=10Code language: JavaScript (javascript)

APIは、pagelimitの2つのクエリ文字列を受け入れます。これらのクエリ文字列を使用すると、サーバーから引用符をページ分割できます。

引用符は、pageクエリ文字列によって決定されるページに分割されます。各ページには、limitパラメーターで指定された数の引用符があります。

ここをクリックして、JavaScriptの無限スクロール機能を使用する最終的なWebアプリケーションをご覧ください。

プロジェクト構造を作成する

まず、infinite-scrollという新しいフォルダーを作成します。そのフォルダー内に、cssjsの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を持つdivloaderを選択します。

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()関数は、pagelimitの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アプリケーションの最終バージョンです。

このチュートリアルは役に立ちましたか?