ホームページ > 記事 > ウェブフロントエンド > 楽な無限スクロール: Intersection Observer を使用して遅延読み込みを実装する方法
特に必要な場合にのみ画像を読み込む場合 (遅延読み込み中)、無限スクロールの作成、またはコンテンツが表示されたときにアニメーションをトリガーしますか?
開発者として、帯域幅を節約し、ページの読み込み時間を短縮するために、画像や新しい記事などのコンテンツをユーザーの画面に表示されている場合にのみ読み込みたいと思うことがよくあります。ただし、ユーザーが Web ページをスクロールするときにこれらの要素がいつ表示されるかを正確に知る必要があるため、これは難しい場合があります。
従来、これにはスクロール イベント リスナーを設定し、要素の位置を手動で計算する必要がありました。これにより、特にコンテンツが多く含まれるページや処理能力が限られているデバイスでは、パフォーマンスが遅くて不安定になる可能性がありました。
// Function to fetch data from an API function fetchData() { console.log('Fetching more data...'); // Simulate an API request using fetch fetch('https://jsonplaceholder.typicode.com/posts?_limit=5&_page=1') .then(response => response.json()) .then(data => { // Display the fetched data on the page }) .catch(error => console.error('Error fetching data:', error)); } // Function to check if the user is near the bottom of the page function checkScrollPosition() { const scrollPosition = window.innerHeight + window.scrollY; const threshold = document.body.offsetHeight - 100; if (scrollPosition >= threshold) { fetchData(); } } // Add the scroll event listener to the window object window.addEventListener('scroll', checkScrollPosition); // Initial call to load some data when the page first loads fetchData();
追加されたイベント リスナーは単純に見えるかもしれませんが、大量のデータを含むページで作業しているとそれに伴う問題に気づくでしょう。イベント リスナーは、イベントをリッスンするために 1 秒間に複数回起動するように設計されていますが、これは機能しますが、JavaScript はシングルスレッド プログラミング言語であるため、ページのパフォーマンスに影響します。また、交差点オブザーバーが実行できるのと同じ効果を実現するには、開発者はビューポートの寸法に注意し、パフォーマンスに影響を与える定期的に実行される計算を追加する必要があります。
課題は、複雑なコードを記述したり、ページの遅延を発生させたりすることなく、可視性の変化を検出することです。これを解決するには、継続的なチェックと計算によってブラウザの速度を低下させずに、可視性を監視するより効率的な方法が必要です。ここで Intersection Observer API が登場し、これらの可視性検出の課題に対して、よりシンプルでパフォーマンスに優しいソリューションを提供します。
Intersection Observer API は、この課題を解決する Web 開発における強力なツールです。このツールは、複雑な計算を行わずに、Web ページ上の要素が表示領域 (Windows ビューポート) に出入りするときの検出を簡素化します。
JavaScript のシングル CPU スレッド機能のパフォーマンスに影響を与える可能性がある継続的なスクロール イベント チェックの代わりに、API を使用すると、ブラウザーが可視性の検出を自動的に処理できるため、画像の遅延読み込み、無限スクロール、要素によってトリガーされるアニメーションなどの機能をよりスムーズに実装できます。広告収益を計算するためのウェブ広告の可視性とレポート。 Intersection Observer API は、これらのタスクをブラウザーにオフロードすることで、特に複雑なサイトやコンテンツの多いサイトで、ページの高速性と効率性を維持するのに役立ちます。さらに、Intersection Observer API はすべてのブラウザーと互換性があり、トレードオフなしでスクロール イベントを追跡するための適切なソリューションとなります。
この記事を終えるまでに、次のことができるようになります:
この投稿では、Intersection Observer API と JavaScript の fetch() 関数を使用してデータを取得し、それを使用してページを更新する方法を説明します。効率的かつ魅力的な Web エクスペリエンスを構築するための手順を説明します。始めましょう!
Intersection Observer は通常、特定の部分が画面上にいつ表示されるかを追跡します。要素がビューポートまたは画面のスクロール可能な部分を満たすかどうかを決定します。画面全体に関する可視性の変化を確認するには、オブザーバーのルート オプションを null に設定する必要があります。これにより、スクロールして表示される項目を追跡するのに役立ちます。
「ルート」という言葉は聞きなれないかもしれませんが、読み進めていくとその理由がわかるでしょう。
API は、ルートとしてビューポートを使用するか別の要素を使用するかに関係なく、同じように機能します。ターゲット要素の可視性がルートとの適切な交差を通過するまで変化するたびに、指定したコールバック関数を呼び出します。
対象要素とそのルートの交差の度合いが交差率です。これは、0.0 ~ 1.0 の値として表示されるターゲット要素のパーセンテージを表します。
To create the intersection observer, you'll call its constructor and pass an option object for behaviour and a callback function to be invoked whenever a threshold is crossed in one direction or the other:
let options = { root: document.querySelector("#ObservableArea"), rootMargin: "0px", threshold: 1.0, }; let observer = new IntersectionObserver(callback, options); //A threshold of 1.0 means that when 100% of the target is visible within the element specified by the root option, the callback is invoked.
The options object passed into the IntersectionObserver() constructor lets you control the trigger and behaviour of the observer's callback to be invoked. The Options object contains the following keys:
root:
The element that is used as the viewport for checking the visibility of the target. It must be a scrollable container or ancestor of the target. Intersection Observer defaults to the browser viewport if not specified or if null.
rootMargin:
Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. Defaults to all zeros.
threshold:
The observer's callback should be executed at a specific % of the target's visibility, indicated by a single or array of numbers. Use a value of 0.5 if you wish to detect only when visibility rises above 50%. To ensure that the callback is executed each time visibility increases by 25%, you can supply the array [0, 0.25, 0.5, 0.75, 1]. The default is 0, which means that the callback will be executed as soon as even a single pixel is displayed. If the value is 1.0, then all pixels must be visible for the threshold to be deemed passed.
After creating the observer, you need a target element to be observed.
let target = document.querySelector("#target"); observer.observe(target); // the callback we set up for the observer will be executed now for the first time // it waits until we assign a target to our observer (even if the target is currently not visible)
Whenever the target meets a threshold specified for the IntersectionObserver, the callback is invoked. The callback receives a list of IntersectionObserverEntry objects and the observer:
let callback = (entries, observer) => { entries.forEach((entry) => { // Each entry describes an intersection change for one observed // target element: // entry.boundingClientRect // entry.intersectionRatio // entry.intersectionRect // entry.isIntersecting // entry.rootBounds // entry.target // entry.time }); };
The list of entries received by the callback includes one entry for each target which reported a change in its intersection status. Check the value of the isIntersecting property to see if the entry represents an element that currently intersects with the root.
N/B!
Be aware that your callback is executed on the main thread. It should operate as quickly as possible; if anything time-consuming needs to be done, use Window.requestIdleCallback().
Also, note that if you specified the root option, the target must be a descendant of the root element.
Now you've got the full gist about intersection observer, I'm excited to get started with the project ?.
In setting up this project, Microsoft Visual Studio Code (VS Code) was used but if you have another text editor or IDE you love, feel free to use that, we won’t judge! ?
However, the steps and procedure to follow are easy, and setting up your project is as easy as taking candy from a baby; that challenge required almost no effort at all. By the end, you'll be able to track when elements appear or disappear on your screen.
You’ll just need three files to get started:
The simplicity of this setup is intentional, as it is designed for beginners. But don't worry—once you’re comfortable, you can use the Intersection Observer in more complex projects utilizing frameworks like React or Vue.
If you're using a Linux or Mac machine copy and paste the code below to your terminal/shell.
#create a directory and enter the mkdir project && cd project #create the files and open VS code touch index.html script.js styles.css #open the current directory on VS Code code .
On Windows, just open VS code on your device, press Ctrl+Shift+N to create a new window, then click on open Folder at the start tab, when the File Explorer opens, create a new folder then click on the new folder and select it. If you don't see the explorer in VS code press Ctrl+Shift+E to open the explorer then click on Create a new file. Give the files any name you want but for the sake of the article I used index.html, style.css and script.js.
After creating your index.html, make sure it looks like the file below
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Movie List with Infinite Scrolling</title> <link rel="stylesheet" href="style.css"> </head> <body> <input type="text" value="" placeholder="enter movie title to search" id="searchInput"> <button id="search">Search</button> <div class="movie-container" id="movieContainer"></div> <div id="loading">Loading...</div> <script src="script.js"></script> </body> </html>
Just follow along with the code below:
body { font-family: Arial, sans-serif; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; flex-direction: column; } .movie-container { width: 80%; max-width: 800px; margin-top: 20px; } .movie-item { display: flex; border: 1px solid #ddd; margin-bottom: 10px; padding: 10px; border-radius: 5px; background-color: #f9f9f9; } .movie-item img { max-width: 100px; margin-right: 20px; } .movie-info { display: flex; flex-direction: column; } .movie-info h3 { margin: 0; margin-bottom: 10px; } .movie-info p { margin: 0; } #loading { text-align: center; margin-top: 20px; display: none; }
const movieContainer = document.getElementById('movieContainer'); const loading = document.getElementById('loading'); const search = document.getElementById('search'); const searchInput = document.getElementById('searchInput'); let currentPage = 1; let observer; const API_KEY = '3676363636'; // Replace with a real API key async function fetchMovies(query, page) { loading.style.display = 'block'; const API_URL = `http://www.omdbapi.com/?s=${query}&apikey=${API_KEY}&page=${page}`; const response = await fetch(API_URL); const {Search} = await response.json(); loading.style.display = 'none'; displayMovies(Search); // Assuming the API returns an array of movies } function displayMovies(movies) { movies.forEach(movie => { const movieItem = document.createElement('div'); movieItem.classList.add('movie-item'); movieItem.innerHTML = ` <img src="${movie.Poster}" alt="${movie.Title}"> <div class="movie-info"> <h3>${movie.Title}</h3> <p><strong>imdbID:</strong> ${movie.imdbID}</p> <p><strong>Year:</strong> ${movie.Year}</p> </div> `; movieContainer.appendChild(movieItem); }); // Reattach the observer to the last movie item if (observer) observer.disconnect(); const lastMovieItem = document.querySelector('.movie-item:last-child'); if (lastMovieItem) observer.observe(lastMovieItem); } // Set up the Intersection Observer observer = new IntersectionObserver(entries => { if (entries[0].isIntersecting) { currentPage++; const query = searchInput.value.trim(); if (query) { fetchMovies(query, currentPage); } } }, { threshold: 1 }); search.addEventListener('click', () => { currentPage = 1; // Reset to the first page on new search movieContainer.innerHTML = ''; // Clear previous results const query = searchInput.value.trim(); if (query) { fetchMovies(query, currentPage); } });
Code Explanation
The index.html is a simple HTML with a head, body, an input to get a query from the user, and a button that the user clicks to fire an action, this action is taken over by the script tag, which calls a function appending the users' query to be fetched by the OMDB API. I used OMDB just for simplicity and illustration, but like I said before, the project structure isn't rigid so readers can choose any method preferred by them.
The style.css contains simple classes for styling the fetched data after being parsed by the script tag. I would not go into details of the styles file as this is not the reason for the article, but developers can always check out w3schools for more tutorials on CSS.
The script.js file is the holy grail of the whole read, for beginners, I'll break the contents of this file into small explainable chunks.
const movieContainer = document.getElementById('movieContainer'); const loading = document.getElementById('loading'); const search = document.getElementById('search'); const searchInput = document.getElementById('searchInput'); let currentPage = 1; let observer; const API_KEY = '3676363636'; // Replace with your real API key
The above code first selects the elements in the index.html and also creates some local variables that will be used in the script.
The currentPage variable is like a page state in the application, that increases as the observer detects an intersection, it is used for querying the OMDB API. The observer variable is defined using the let keyword to be assigned to the IntersectionObserver() constructor function in the later part of the script.
async function fetchMovies(query, page) { loading.style.display = 'block'; const API_URL = `http://www.omdbapi.com/?s=${query}&apikey=${API_KEY}&page=${page}`; const response = await fetch(API_URL); const {Search} = await response.json(); loading.style.display = 'none'; displayMovies(Search); // Assuming the API returns an array of movies } function displayMovies(movies) { movies.forEach(movie => { const movieItem = document.createElement('div'); movieItem.classList.add('movie-item'); movieItem.innerHTML = ` <img src="${movie.Poster}" alt="${movie.Title}"> <div class="movie-info"> <h3>${movie.Title}</h3> <p><strong>imdbID:</strong> ${movie.imdbID}</p> <p><strong>Year:</strong> ${movie.Year}</p> </div> `; movieContainer.appendChild(movieItem); }); //observer code here }
In the code above, the fetchMovies() function contains the query and page parameters, first, the loading element in the HTML is made visible with the display=block, and then API_URL is a variable that holds the preferred API service in string format.
Depending on the API you want to use, some of them offer their services to registered users only. For services like OMDB you have to create an account with them where you'll be given a key upon completion of the registration. That key will be used in the API_URL string. OMDB API sends an array of movies containing 10 items only, the page variable is used to query the API to send the next 10 items after viewing the previous ones. If you noticed I interpolated the string using ${} where I added the query from the user input and also my key from the OMDB movie Service.
const API_URL = `http://www.omdbapi.com/?s=${query}&apikey=${API_KEY}&page=${page}`;
The response variable awaits the response after fetching the API_URL, the data variable parses the response to JSON format using the .json() method on the response variable.
Once the data is fetched the display of the loading element is set to none to hide the loading element.
After that, the displayMovies is called with the destructured Search from the returned JSON. The work of the display function is to accept an array of movies in JSON format and parse each movie to an HTML template containing the movie poster and details of each movie in the array.
// Reattach the observer to the last movie item if (observer) observer.disconnect(); const lastMovieItem = document.querySelector('.movie-item:last-child'); if (lastMovieItem) observer.observe(lastMovieItem);
This code is attached to the displayMovies() function, I'm explaining it separately for adept understanding. In the above code, the observer is disconnected from the last-item queried and attached to the current last-item. This is to ensure the observer is up-to-date at each instance of observing.
// Set up the Intersection Observer observer = new IntersectionObserver(entries => { if (entries[0].isIntersecting) { currentPage++; const query = searchInput.value.trim(); if (query) { fetchMovies(query, currentPage); } } }, { threshold: 1 });
This is where the IntersectionObserver constructor function is defined. This function continues the query with the current page after the user initially fires the click event. This enables the application to fetch movie items when they need to be seen in the view-port rather than clogging the application with data, that the user may not even reach before finding the information they are looking for in this case a movie.
search.addEventListener('click', () => { currentPage = 1; // Reset to the first page on the new search movieContainer.innerHTML = ''; // Clear previous results const query = searchInput.value.trim(); if (query) { fetchMovies(query, currentPage); } });
This last code is a simple code that adds an event listener to listen for the click event on the search button element. This is like the entry point of the application, as the user enters input in the search box and clicks on the search button, the function attached to the event listener function is invoked. First, it resets the current page to 1, then clears the parsed data from the previous query. Lastly, it fetches the new query with its current page as 1. The intersection observer continues the fetching queries from the API_URL, after reaching the last-item of the parsed HTML from the Search data returned in the query.
The GIF above shows how the application works, and how the data are fetched when the user scrolls to the last movie item, this is performed by the Intersection Observer calling the fetch function after the user has queried the API.
このプロジェクトでは、Intersection Observer を使用して、親要素によって子要素またはターゲット要素を観察する方法を示します。オブザーバーは、小さなボックスを備えたボックスの上に置かれた目のようなもので、小さなボックスが表示される (交差する) と関数が呼び出されます。この記事では、呼び出された関数は、配列内で取得および解析されたデータが終了するまで、ユーザーが現在のページを追加することによってクエリを取得し続けます。
オブザーバーの全体的な理由は、API_URL から再度フェッチする前に最後の項目を観察することです。データとポスター画像の両方を遅延ロードするこの方法により、帯域幅の無駄が削減され、ページの読み込み時間が増加し、さらに重要なことに、ユーザーが問い合わせたいことだけを入力します。たとえば、ユーザーが「ジョン ウィック」をクエリしますが、全体的なアイデアは、ページにすべてのジョン ウィック映画が表示される前に「ジョン ウィック パラベラム」を取得することです。ユーザーは検索前に目的の映画を見つけている必要があります。
スクロール イベント リスナーと比較すると、Intersection Observer API はより効率的で使いやすいです。これは、スクロールすると常に起動してページの速度が低下する可能性があるスクロール イベント リスナーとは異なり、Intersection Observer は必要な場合にのみ要素の可視性をチェックするため、パフォーマンスが向上するためです。要素が表示されると自動的に処理され、画像の遅延読み込み、アニメーションのトリガー、無限スクロールなどのタスクをより簡単かつ迅速に実装できるようになります。頻繁な更新を管理するために追加のコーディング手法を心配する必要はなく、異なるブラウザ間でも一貫して動作します。
以上が楽な無限スクロール: Intersection Observer を使用して遅延読み込みを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。