Skip to content

Infinite scroll with intersectionObserver API

Yongho Lee edited this page Nov 30, 2019 · 3 revisions

스크롤이 포함된 리스트 컴포넌트를 개발한다고 생각해봅시다. 아래와 같이 3개의 ListItem이 담긴 리스트는 렌더링하는 데 전혀 문제가 없을 것입니다. (한 눈에 들어올 것입니다! 👀)

    <List>
    	<ListItem>1</ListItem>
    	<ListItem>2</ListItem>
    	<ListItem>3</ListItem>
    </List>

그런데 만약 ListItem이 1000개 라고 가정해봅시다. 일반적인 상황이라면 한눈에 다 들어오지 않을 것입니다. 그렇다면 웹페이지를 로딩할 때, 한번에 1000개를 다 로딩시키는 건 과연 좋은 방법일까요? 다양한 의견이 있을 수도 있겠지만, No 라고 생각합니다.

왜냐하면 위의 예제에서는 단순한 텍스트지만, 보통의 환경에선 이미지도 들어갈거고 스타일링 또한 많을 것입니다. 이것들을 다 로딩시키는 건 굉장한 자원낭비라고 생각합니다. 또한 특수한 경우를 제외하곤 스크롤을 끝까지 내려서 다 보는 유저도 많이 없을 것입니다.

따라서 List 컴포넌트를 구현할 때, 리스트 아이템을 동적으로 가져오는 것은 필수입니다. 그렇다면 어떻게 동적으로 가져올 수 있을까요? 이미 많은 페이지를 경험하면서 리스트 스크롤을 내리다 보면 맨 끝에 도달할 때 쯤 새로운 아이템들을 가져오는 것을 알 수 있을 것입니다.

리스트 스크롤이 맨 아래쯤 도달한 것은 어떻게 알아낸걸까요? 이전에는 주로 특정 컨테이너에 이벤트를 달아서 스크롤을 계속해서 탐지하며 일정 부분에 도달했을 때를 감지했습니다. 이렇게 되면 의미없는 스크롤 이벤트도 계속해서 읽기 때문에 자원 낭비가 발생하게 됩니다.

그렇다면 조금은 더 스마트하게 특정 영역에 도달한 걸 알 수 없을까요? 바로 Web API 중 하나인 Intersection Observer API 이 있습니다.

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

Target element와 특정 영역의 교차점에서의 변화를 비동기적으로 관찰한다. 아주 쉽게 보면 "Element가 특정 영역에서 보이는 가?"를 쉽게 알려준다는 것입니다. 아래의 설명에서는 viewport, 즉 화면상 기준으로 특정 영역을 설명하겠습니다.

그렇다면 이것을 사용하여 Infinite scroll 을 어떻게 구현할 지에 대해 간단한 코드로 알아봅니다.

    <List class="list">
    	<ListItem>1</ListItem>
    	<ListItem>2</ListItem>
    	<ListItem>3</ListItem>
    </List>
    <div class="observer"></div>

이런식으로 observer라는 이름을 가진 div element가 화면에 보이게 된다면 ListItem을 추가하는 아이템을 실행시킬 수 있을 것입니다.

    const addListItem = () => {
    	document.querySelector('.list').innerHTML += `
    		<ListItem>4</ListItem>
    		<ListItem>5</ListItem>
    		<ListItem>6</ListItem>
    	`;
    }

그러면 이제 어떻게 Intersection Obeserver API를 사용하는 지에 대해 알아봅시다.

    const options = {
    	root: document.querySelector('#scrollArea'),
      rootMargin: '0px',
      threshold: 1.0
    };
    
    const observer = new IntersectionObserver(callback, options)

위의 코드를 설명하기 전에 IntersectionObserver의 두 번째 파라미터인 options부터 설명해볼까요? options에는 세 가지가 들어갑니다.

root

The element that is used as the viewport for checking visiblity of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.

target element와 교차점을 감지할 Element을 설정합니다. default value로는 null 값인데, null은 root값이 설정된다면 브라우저의 viewport로 영역이 설정되는 것을 의미합니다. 따라서 보이는 화면 전체 내에서의 보이는 가? 를 보려면 null을 주어야하고, 특정 영역 내에서의 element가 보이는 가를 보려고 한다면 특정 영역의 Element를 주어야 합니다.

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.

이름 그대로의 root의 margin값을 설정합니다. 우리가 익히 알고 있는 css의 margin과 같은 개념입니다. 예를 들어 rootMargin: "0px 0px 200px 0px" 라고 설정했다면 root의 영역에서의 교차점을 탐지하는 것이 아니라 마진의 영역을 제외한 안의 영역에서의 교차점을 탐지하게 됩니다. 간단하게 주의할 것은 문자열로 작성을 해야합니다.

threshold

Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible.

threshold는 target element와 root가 얼마나 교차했을 때(얼마나 보였을 때), callback을 실행할 지를 결정하는 값입니다. 기본적으로는 0부터 1까지의 float값을 할당합니다. 0%부터 100%, 보였을 때를 의미합니다. 신기하게도 배열도 할당을 할 수 있는데 [0, 0.25, 0.5, 0.75, 1] 라고 할당을 한다면 25% 교차할 때마다 무언가의 동작을 할 수 있습니다.

종합적으로 다시 설명하자면, root와 target element가 threhold만큼 교차하게 된다면 callback이 실행됩니다!

그렇다면 callback은 어떻게 설정할까요?

callback

콜백 함수의 파라미터로는 entries와 observer가 있습니다.

entries: Element[] target으로 지정한 DOM elemet . observer: IntersectionObserver 자기 자신인 observer

    const callback = (entries, observer) => { 
      entries.forEach(entry => {
    		if (entry.isIntersecting) {
    			addListItem();
    		}
      });
    };

이런식으로 isIntersecting 이라는 메소드를 통해서 각 엔트리 엘리먼트들이 교차점에 도달했냐에 따라서 관련 함수들을 실행시킬 수 있습니다.

References

BookUs!

개요
기획서

Tech

실용적인 리액트 테스트 전략
DevOps
Infra Structure
컴포넌트 작성법
Client Sturcture

Documents

데이터베이스 스키마
Yarn workspace 명령어
Docker를 이용한 서버 개발 환경
Linting Tools

Stress Testing Log

테스트 로그

1차 테스트

📝 Agile Process

스프린트 0주차: 기획 단계
스프린트 1주차: 개발 환경 구축
스프린트 2주차: 개발
스프린트 3주차: 개발
스프린트 4주차: 개발
스프린트 5주차: 개발
👉 스프린트 6주차 🔥

👷‍♂️ Technique Writing

🤝 Rules

Clone this wiki locally