>  기사  >  웹 프론트엔드  >  손쉬운 무한 스크롤: Intersection Observer를 사용하여 지연 로딩을 구현하는 방법

손쉬운 무한 스크롤: Intersection Observer를 사용하여 지연 로딩을 구현하는 방법

王林
王林원래의
2024-08-26 21:34:06715검색

특히 필요할 때만 이미지를 로드하는 경우(게으른- 로딩), 무한 스크롤 생성, 콘텐츠가 나타날 때 애니메이션 트리거 등이 있나요?

소개

개발자로서 대역폭을 절약하고 페이지 로딩 시간을 개선하기 위해 이미지나 새 기사와 같은 콘텐츠가 사용자 화면에 표시될 때만 로드하려는 경우가 많습니다. 그러나 사용자가 웹페이지에서 스크롤할 때 이러한 요소가 언제 표시되는지 정확히 알아야 하기 때문에 이는 까다로울 수 있습니다.

스크롤 이벤트에 EventListener를 추가하는 전통적인 방법

일반적으로 이를 위해서는 스크롤 이벤트 리스너를 설정하고 요소 위치를 수동으로 계산해야 하므로 특히 콘텐츠가 많은 페이지나 처리 능력이 제한된 기기에서 성능이 느리고 투박해질 수 있습니다.

// 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는 이러한 문제를 해결하는 웹 개발의 강력한 도구입니다. 이 도구는 복잡한 계산 없이 웹페이지의 요소가 가시 영역(창 뷰포트)에 들어오거나 나갈 때 감지를 단순화합니다.

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로 설정해야 합니다. 이는 스크롤하여 항목을 볼 때 항목을 추적하는 데 도움이 됩니다.
"루트"라는 단어가 낯설게 들릴 수도 있지만, 계속 읽어보면 왜 그런지 이해하게 되실 겁니다.

뷰포트를 사용하든 다른 요소를 루트로 사용하든 상관없이 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으로 문의하세요.