首页  >  文章  >  web前端  >  轻松无限滚动:如何使用 Intersection Observer 实现延迟加载

轻松无限滚动:如何使用 Intersection Observer 实现延迟加载

王林
王林原创
2024-08-26 21:34:06625浏览

我们如何高效、轻松地检测网页上的元素何时变得可见或隐藏,而不减慢页面速度或编写复杂的代码来处理滚动事件和可见性计算,特别是在仅在需要时加载图像的情况下(惰性加载)加载)、创建无限滚动或在内容出现时触发动画?

介绍

作为开发人员,我们通常希望仅在用户屏幕上可见时加载图像或新文章等内容,以节省带宽并缩短页面加载时间。然而,这可能很棘手,因为需要准确地知道当用户在网页上滚动时这些元素何时进入视图。

在滚动事件上添加事件监听器的传统方法

传统上,这需要设置滚动事件侦听器并手动计算元素位置,这可能会导致性能缓慢且笨拙,尤其是在内容较多的页面或处理能力有限的设备上。

// 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