교차로 관찰자 | 웹 애플리케이션에서 무한 스크롤 만들기

Nov 25 2022
당신이 동굴 사람이 아니거나 Jeff Bezos를 싫어하지 않는다면(그렇더라도 상관없습니다) 온라인 쇼핑을 했을 것입니다. 이러한 온라인 상점에서 항목을 검색하면 결과 목록이 표시됩니다.
Unsplash의 SpaceX 사진

당신이 동굴 사람이 아니거나 Jeff Bezos를 싫어하지 않는다면(그렇더라도 상관없습니다) 온라인 쇼핑을 했을 것입니다. 이러한 온라인 상점에서 항목을 검색하면 결과 목록이 표시됩니다. 나처럼 이상한 사람이 아니고 너무 구체적인 "인혈에 젖은 호랑이 이빨 "을 찾고 있지 않다면 검색어에 수천 개의 제품이 나올 가능성이 있습니다. 귀하의 브라우저가 이 1000개 이상의 항목을 모두 로드한다고 생각하십니까? 아니요 , 브라우저가 사용자 인터페이스를 처리하고 계산하기에는 데이터가 너무 많습니다. 그래도 결과가 끝날 때까지 계속 항목을 얻습니다. 이것을 무한 스크롤 이라고 합니다 .

다음은 YouTube 홈페이지의 무한 스크롤 예시입니다.

무한 스크롤을 설명하는 GIF

JavaScript를 사용하여 이를 달성하는 몇 가지 기술이 있지만 가장 효율적인 것은 IntersectionObserver라는 달콤한 웹 API를 사용하는 것입니다. 이 API를 사용하여 우리만의 미니 무한 스크롤 목록을 구현하는 방법을 살펴보겠습니다.

개념은 뷰포트에 들어올 때마다 html 요소를 관찰한 다음 콜백을 트리거하는 것입니다. 이 관찰자를 만드는 방법은 다음과 같습니다.

다음과 같은 html 파일을 만들어 봅시다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Infinte Scroll</title>
</head>
<style>
    .product {
        padding: 20px;
        border: 1px solid black;
        margin-bottom: 20px;
        opacity: 0;
        margin-left: 100px;
        transition: all 300ms;
    }
    .visible {
        opacity: 1;
        margin-left: 0px;
    }
</style>
<body>
    <div id="container">
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
    </div>

    <script src="./index.js"></script>
</body>
</html>

먼저 클래스 이름으로 모든 제품 카드를 가져옵니다.

const cards = document.querySelectorAll('.product');

const productCards= document.querySelectorAll('.product');

const observer = new IntersectionObserver((entries) => {
    // Todo
}, {
    threshold: 0.95,
});

productCards.forEach((card) => {
  observer.observe(card);
})

생성자의 또 다른 매개변수는 객체입니다. 이러한 개체는 이 특정 관찰자에 대한 옵션을 나타냅니다. 이 경우 임계값만 있습니다. 여기에서 임계 값은 0에서 1 사이의 값을 가질 수 있는 숫자입니다. 이것은 우리가 관찰하고 있는 항목이 교차 하는 것으로 간주되기 위해 뷰포트에 표시되어야 하는 항목의 백분율을 나타냅니다 . 우리는 0.95의 값을 부여했습니다. 즉, 제품 카드의 95%가 뷰포트에 들어올 때 해당 카드가 뷰포트와 교차하는 것으로 간주됩니다.

index.html 파일 로 돌아가 봅시다 . 또한 styled 태그에 정의된 또 다른 스타일 클래스가 표시 됩니다. 이 스타일을 제품 카드에 적용하면 약간의 애니메이션( 전환 속성)으로 카드가 다시 표시됩니다.

뷰포트에서 95% 이상인 모든 제품 카드가 표시되고 나머지는 숨겨지도록 관찰자를 변경해 보겠습니다.

const productCards = document.querySelectorAll('.product');

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        entry.target.classList.toggle('visible', entry.isIntersecting);
    })
}, {
    threshold: 0.95
});

productCards.forEach((card) => {
    observer.observe(card);
});

그래도 한 가지 문제가 있습니다. 위로 스크롤하면 항목이 교차할 때 보이는 클래스 를 토글하는 로직이 있기 때문에 교차하지 않을 때도 이 클래스를 제거합니다. 따라서 위로 스크롤하면 카드가 상단에서 사라지는 것을 볼 수 있습니다. 이 문제를 해결하기 위해 이미 교차한 카드를 관찰하지 않습니다.

const productCards = document.querySelectorAll('.product');

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        entry.target.classList.toggle('visible', entry.isIntersecting);
        // unobserving entries which have already intersected
        if (entry.isIntersecting) observer.unobserve(entry.target);
    })
}, {
    threshold: 0.95
});

productCards.forEach((card) => {
    observer.observe(card);
});

먼저 마지막 카드를 관찰할 다른 관찰자를 만듭니다.

const lastCardObserver = new IntersectionObserver((entries) => {
    const lastCard = entries[0];
    if (!lastCard.isIntersecting) return;
    loadMoreCards();
    lastCardObserver.unobserve(lastCard.target);
    lastCardObserver.observe(document.querySelector('.product:last-child'));
}, {
    threshold: 0.95
});

lastCardObserver.observe(document.querySelector('.product:last-child'));

function loadMoreCards() {
    const container = document.getElementById('container');
    for (let i = 0; i < 10; i++) {
        const element = document.createElement('div');
        element.classList.add('product');
        element.innerText = 'Product';
        observer.observe(element);
        container.appendChild(element);
    }
}

이것이 지금의 모습입니다!

완벽한 무한 스크롤!

이제 실제 애플리케이션에서는 더 많은 카드를 로드하는 대신 API를 사용하여 새 데이터를 가져옵니다.

응용 프로그램에서 이 API를 창의적으로 사용할 수 있는 방법 알려주십시오 .