首頁  >  文章  >  web前端  >  輕鬆無限滾動:如何使用 Intersection Observer 實現延遲加載

輕鬆無限滾動:如何使用 Intersection Observer 實現延遲加載

王林
王林原創
2024-08-26 21:34:06733瀏覽

我們如何有效、輕鬆地檢測網頁上的元素何時變得可見或隱藏,而不減慢頁面速度或編寫複雜的程式碼來處理滾動事件和可見性計算,特別是在僅在需要時加載圖像的情況下(惰性加載)加載)、創建無限滾動或在內容出現時觸發動畫?

介紹

作為開發人員,我們通常希望僅在用戶螢幕上可見時加載圖像或新文章等內容,以節省頻寬並縮短頁面加載時間。然而,這可能很棘手,因為需要準確地知道當使用者在網頁上滾動時這些元素何時進入視圖。

在滾動事件上新增事件監聽器的傳統方法

傳統上,這需要設定滾動事件偵聽器並手動計算元素位置,這可能會導致效能緩慢且笨拙,尤其是在內容較多的頁面或處理能力有限的裝置上。

// 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();

新增的事件監聽器可能看起來很簡單,但是,當您在包含大量資料的頁面上工作時,您會注意到它帶來的問題。事件監聽器被設計為在一秒鐘內觸發多次以實現事件監聽,但由於 JavaScript 是單線程程式語言,因此會影響頁面效能。另外,為了達到與交叉觀察器相同的效果,開發人員需要注意視口尺寸並添加定期執行的計算,但仍然會影響效能。

面臨的挑戰是在不編寫複雜程式碼或使頁面滯後的情況下檢測可見性變化。解決這個問題需要一種更有效的方法來監控可見性,並且透過不斷的檢查和計算不會減慢瀏覽器的速度。這就是 Intersection Observer API 的用武之地,它為這些可見性檢測挑戰提供了更簡單、效能更友善的解決方案。

Intersection Observer API 是 Web 開發中的強大工具,可以解決這個挑戰。該工具簡化了網頁上的元素進入或退出可見區域(Windows 視窗)時的偵測,無需複雜的計算。

不斷的滾動事件檢查會影響JavaScript單CPU線程功能的效能,該API讓瀏覽器自動處理可見性檢測,從而可以更流暢地實現延遲加載圖像、無限滾動、元素觸發的動畫等功能可見性並報告網路廣告的可見性以計算廣告收入。透過將這些任務轉移到瀏覽器,Intersection Observer API 有助於保持頁面快速且高效,尤其是在複雜或內容較多的網站上。此外,Intersection Observer API 與所有瀏覽器相容,使其成為追蹤滾動事件的熟練解決方案,無需任何權衡。

目標

讀完本文,您將:

  • 了解如何使用 Intersection Observer API 來檢查元素何時進入檢視。
  • 了解 Intersection Observer API 的術語和語法。
  • 了解如何在該元素可見時從 API 取得資料。
  • 能夠解析 JSON 資料並將其新增至您的 HTML 結構中。

這篇文章將向您展示如何使用 Intersection Observer API 和 JavaScript 的 fetch() 函數來取得資料並用它更新您的頁面。我們將逐步完成建立高效且有吸引力的網路體驗的步驟。讓我們開始吧!

了解 Intersection Observer API、概念和用法

路口觀察者

Intersection Observer 通常會追蹤特定片段何時出現在螢幕上。它確定元素是否填滿視口或螢幕的可捲動部分。您必須將觀察者的根選項設為 null 才能看到整個螢幕的可見度變化。這有助於在項目滾動到視圖中時追蹤項目。
雖然「root」這個詞可能聽起來很陌生,但讀下去你就會明白為什麼了? 。

無論您使用視窗還是其他元素作為根,API 的功能都是相同的;它會呼叫一個回調函數,每次目標元素的可見性變化到它與根元素相交的適當程度時,您都提供該回呼函數。

目標元素與其根元素的交集程度即為交集比。這是目標元素的百分比表示,其可見值介於 0.0 和 1.0 之間。

Creating an intersection observer

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.

Intersection observer options object

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.

Observing a target element

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.

Project Setup

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:

  • index.html
  • styles.css
  • script.js

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.

Create a Project Directory:

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.

  • index.html

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>

  • styles.css

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;
}

  • script.js
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.

Variable Definition and Elements Selection:

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.

Fetching and Parsing Fetched Data:

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.

Effortless Infinite Scrolling: How to Implement Lazy Loading with Intersection Observer

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 獲取之前觀察最後一項,這種延遲加載數據和海報圖像的方法減少了頻寬浪費,增加了頁面加載時間,更重要的是顯示了用戶只需要查詢他們想要查詢的內容。例如,用戶查詢“John Wick”,但整個想法是在頁面顯示所有 John Wick 電影之前獲取“John Wick Parabellum”,用戶必須在搜尋之前找到想要的電影。

與捲動事件監聽器相比,Intersection Observer API 更有效率且更易於使用。這是因為,與滾動事件偵聽器(滾動事件偵聽器在滾動時不斷觸發並可能減慢頁面速度)不同,Intersection Observer 僅在必要時檢查元素的可見性,從而提高效能。它會自動處理元素進入視圖的時間,從而使延遲加載圖像、觸發動畫和無限滾動等任務的實現變得更加簡單和快速。您無需擔心額外的編碼技巧來管理頻繁的更新,並且它在不同的瀏覽器中一致地工作。

以上是輕鬆無限滾動:如何使用 Intersection Observer 實現延遲加載的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn