본문 바로가기
개발 공부 일지/JavaScript

자바스크립트 - 무한 스크롤

by yelimu 2024. 8. 4.

토요일 보충학습 시간에 강사님이랑 무한스크롤을 구현했다. (강사님 코드를 따라 쳐본거지만) 

<!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>Document</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="card-container">
      <div class="card">First Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      // ... 중략   ...
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Card</div>
      <div class="card">Last Card</div>
    </div>
    <script src="infinityScroll.js" defer></script>
  </body>
</html>

구조를 살펴보면 div (.card-container) 안에 div(.card)가 쭉 늘어져있고, 처음과 마지막에만 First / Last card라고 표시해준다. 

.card-container {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    align-items: flex-start;
  }
  .card {
    background-color: white;
    border: 1px solid black;
    border-radius: 0.25rem;
    padding: 0.5rem;
    transform: translateX(100px);
    opacity: 0;
    transition: 150ms;
  }
  .card.show {
    transform: translateX(0);
    opacity: 1;
  }

show class 로 별도로 추가할 스타일 지정을 해주었다. 

const cards = document.querySelectorAll(".card");
const cardContainer = document.querySelector(".card-container");

//Intersection Observer API
//Web API의 일종 (웹 브라우저가 기본적으로 가지고 있는 API)
//제 3의 눈같은.. 계속 지켜본다 
// API쓸때 new로 가져다가 쓴다 
const cardObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
        entry.target.classList.toggle("show", entry.isIntersecting);  // 참이면 show 클래스 추가
        if (entry.isIntersecting) cardObserver.unobserve(entry.target);
    })
});
//감시 대상이 걸렸을때 동작할 콜백함수 써주기

const lastCardObserver = new IntersectionObserver((entries)=>{
    const lastCard = entries[0];

    if (!lastCard.isIntersecting) return;

    for (let i = 0; i < 10; i++) {
        const card = document.createElement("div");
        card.textContent = "New Card";
        card.classList.add("card");
        cardObserver.observe(card);
        cardContainer.append(card);
        }
        lastCardObserver.unobserve(lastCard.target);
  
        lastCardObserver.observe(document.querySelector(".card:last-child"));
    });
// 감시 세팅만 해둔거임 -> 붙여줘야됨

// 감시 오픈~
lastCardObserver.observe(document.querySelector(".card:last-child"));

cards.forEach((card) => {
    cardObserver.observe(card);
})

card랑 card-container DOM 요소를 변수에 할당해둔다~ 

 

//지피티 예시 코드

// 관찰할 때 실행될 콜백 함수
const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is in view!');
      // 관찰을 중지할 수도 있음
      observer.unobserve(entry.target);
    }
  });
};

// 옵션 설정
const options = {
  root: null, // 뷰포트
  rootMargin: '0px',
  threshold: 0.5 // 요소의 50%가 보일 때 콜백 실행
};

// IntersectionObserver 객체 생성
const observer = new IntersectionObserver(callback, options);

// 관찰할 요소 선택
const target = document.querySelector('.target');

// 요소 관찰 시작
observer.observe(target);

일종의 공식같은 느낌으로 기억해두면 좋을것같다.  

여기서 IntersectionObserver는 배열을 생성한다. 주로 entries라고 칭한다. 콜백함수의 첫번째 인자로 받는다. 

더보기

Intersection Observer API는 웹 브라우저가 기본적으로 가지고 있는 API이다 

 

웹 페이지에서 요소가 뷰포트(또는 다른 지정된 요소)와 교차하는지 여부를 비동기적으로 관찰하는 데 사용되는 API입니다. 이 API를 사용하면 스크롤이나 요소의 가시성 변화를 감지하여 효율적으로 작업을 수행할 수 있습니다. 주요 용도로는 무한 스크롤, 이미지 및 콘텐츠의 지연 로딩, 애니메이션 트리거 등이 있습니다.

 

뷰포트와 교차한다..라는건 사용자가 해당 요소를 볼 수 있는 상태 라고 이해하면 될 것 같다. 

무한스크롤 코드에서는 IntersectionObserver를 두개를 세팅했고, 콜백함수를 별도로 선언하지 않고 바로 이어서 써주었다. 

1. cardObserver : entries를 받아서, 각각의 entry를 돌면서 isIntersecting 이 참일때 show class를 toggle 한다 

( isIntersecting은 IntersectionObserverEntry 객체의 속성 중 하나)

해당 요소가 보이면 (show) 관찰을 중지한다.

 

2. lastCardObserver : 마지막 카드를 관찰해서 New card 를 만들어주고 관찰을 시작한다.(.observe)  

반복문이 끝나면 이전 마지막 카드 관찰을 끝내고, 새로 만들어진 new card 관찰을 시작한다. 

 


추가로 DOM 개념 설명해주신 부분에 지피티를 곁들여본다 ~

 

브라우저가 화면을 렌더링 하는 과정

1. 웹브라우저가 HTML을 읽어서 = 파싱 → DOM TREE를 생성 (객체 모델로 변환) 

2. CSS 를 읽어서 = 파싱 →  CSSOM TREE를 생성 (객체 모델로 변환) 

3. 두가지를 결합해서 RENDER TREE 를 생성 : 화면에 실제로 표시될 요소들만을 포함

   과정: DOM 트리의 각 노드를 순회하면서, 각 노드에 해당하는 CSS 스타일을 CSSOM 트리에서 찾는다

  시각적으로 보이지 않는 노드(예: display: none으로 설정된 요소)는 렌더 트리에 포함되지 않는다.  

4. 브라우저는 각 요소의 크기와 위치를 계산한다=   레이아웃 (또는 리플로우 Reflow) 계산 

5. 페인팅 단계 (또는 래스터라이제이션 Rasterization) : 레이아웃 단계 완료 후 각 요소를 화면에 실제로 그린다. 

 


toggleBtn.addEventListener("click", function(){ toggleBtn.classList.toggle("red"); })

 

const cards = document.querySelectorAll(".card");

 

자바스크립트에서 이런 addEventListener, classList.toggle, querySelectorAll 등의 메소드를 사용할 수 있는건

각각의 객체 (DOM 요소 객체, document 객체 등) 가 해당 메소드를 속성으로 가지고 있기 때문이다.