Web/[JS] FrontEnd

Intersection Observer

ihl 2021. 7. 16. 18:04

관측자

  Observer란 관측자를 뜻한다. 특정 대상을 항상 지켜보고 있는 존재이다. 브라우저에는 Intersection Observer 라는 API가 존재한다. Intersection Observer는 root와 하나 이상의 Element가 교차하는 시점을 관측하고 있다. root는 지정하지 않는다면 뷰포트를 의미한다. 즉, Intersection Observer는 관찰 대상이 뷰포트 내에 들어오는지 비동기적으로 관찰하는 요소로 활용할 수 있다. Intersection Observer는 대표적으로 무한스크롤, 컨텐츠 Lazy Load의 구현에 사용된다.

 

1. Infinity Scroll

  무한 스크롤이란 페이지의 세로 끝에 도달했을 때 새로운 컨텐츠를 불러오는 기능이다. 무한 스크롤을 구현하는 방식 중 하나는 스크롤 이벤트를 등록하여 스크롤의 위치가 변할 때마다 스크롤의 위치를 확인하고, 스크롤이 마지막에 도달하면 새로운 컨텐츠를 불러오는 함수를 실행하는 것이다. 그런데 이러한 방식에는 리소스 낭비의 위험성이 있다.

 

  스크롤 이벤트를 등록하면 스크롤이 움직일 때마다 이벤트 핸들러가 호출되므로 중복 호출이 발생한다. 또한 마지막에 도달했을 때 새로운 컨텐츠를 불러오는 함수도 여러 번 실행될 수 있다. 이를 해결하기 위해선 Throttle(특정 기간 내에 발생한 스크롤 이벤트의 이벤트 처리 함수는 1번만 호출)나 Debounce(연속된 중복 호출 시 마지막 혹은 처음 호출만 실행)를 구현해주어야 한다. Intersection Observer를 이용하면 이들을 따로 구현할 필요가 없다. 마지막 요소가 뷰포트에 들어오면 새로운 컨텐츠를 부르는 함수를 호출하도록 Observer를 등록하면 되기 때문이다.

 

See the Pen Infinity Scroll by ImHyeLim1209 (@imhyelim1209) on CodePen.

  Intersection Observer를 이용한 무한스크롤을 구현하기 위해선 먼저 Intersection Observer로 지켜볼 대상이 필요하다. 이 대상은 현재 스크롤의 가장 마지막에 있어야 한다. 위 코드에서는 end라는 div를 대상으로 삼았고, 컨텐츠 div들의 마지막에 1px의 크기로 붙여주었다.

 

  이 후에는 IntersectionObser 객체를 생성한다. 생성자는 콜백함수를 인자로 받는다. 콜백함수의 첫번째 요소는 Observer로 지켜보고 있는 요소들의 배열이다. 지켜보는 대상이 뷰포트 내에 있다면(entry.isIntersecting) 컨텐츠를 새롭게 로드한다. 컨텐츠의 마지막을 뜻하는 end div도 마지막에 다시 붙여준다.

 

  IntersectionObserver 객체를 생성했다면 io.observe(end) 라는 문장으로 Observer의 관찰대상을 end로 지정해준다. 이제 스크롤을 해보면 원래 3개만 있던 컨텐츠가 스크롤에 의해 무한하게 늘어난다.

 

2. LazyLoad

LazyLoad

  Content Lazy Load는 페이지를 로드할 때 모든 컨텐츠를 함께 로드하는 것이 아니라, 페이지 로드 후 필요한 순간에 필요한 컨텐츠만 로드하는 방법이다. 한 페이지에 여러 개의 이미지가 있는 경우 뷰포트 내에 바로 보여지는 이미지만 로드하고, 그렇지 않은 이미지는 로드하지 않는 것이다. Lazy Load를 사용하면 초기 페이지 로딩 속도가 더 빨라진다.

 

<!--페이지로드와 함께 로드되는 이미지-->
<img src="이미지주소"/>

<!--페이지로드와 함께 로드되지 않은 이미지-->
<!--data-src값을 src로 할당해주어야 이미지가 로드된다.-->
<img data-src="이미지주소"/>

  이미지 Lazy Load는 img 태그의 속성을 활용한다. img 태그의 src 속성에 리소스 주소 값이 있다면 해당 이미지을 로드하고, src외의 다른 속성에 할당된 이미지 주소는 로드되지 않는다. 따라서 페이지 로드와 동시에 불러와야하는 이미지는 src로 지정하고, Lazy Load해야하는 이미지는 다른 속성(data-src, value 등)에 이미지 주소를 할당한 후 이미지 로드가 필요한 순간에 해당 이미지 주소를 src 속성에 할당해주면 된다.

 

See the Pen IntersectionObserver by ImHyeLim1209 (@imhyelim1209) on CodePen.

  위 코드는 Lazy Load를 구현한 것이다. 스크롤을 내리면서 Network 창을 확인해보자. 스크롤을 내릴 때 이미지가 새롭게 로드되는 통신이 발생한다.

 

  Lazy Load에서 IntersectionObserver의 관찰 대상은 img 태그이다. load라는 클래스 이름을 가진 img 태그들을 모두 지켜보다가 뷰포트 안으로 들어오면 src속성을 data-set 속성의 값(dataset.src)으로 할당해준다. 이와 동시에 borderRadius도 20px로 변경해주었다. 이미지가 한 번 로드되면 더 이상 로드할 필요가 없기 때문에 observer.unobserve() 메소드로 해당 img 태그를 관찰 대상에서 제외시킨다.

 

const options = {
  root: null,
  rootMargin: "0px 0px 500px 0px",
  threshold: 0
}

  이미지가 뷰포트 내에 들어왔을 때 이미지를 로드했을 때 환경에 따라 사용자에게 이미지 로드가 덜 된 상태의 img 태그를 보여주게 될 수도 있다. 따라서 뷰포트 내에 들어왔을 때가 아니라, 뷰포트에 들어오기 500px 직전에 로드하도록 구현하는 것이 더 좋다. 이는 IntersectionObserver 생성 시 옵션을 지정하는 options라는 객체로 지정할 수 있다.

 

  options의 요소는 root, rootMargin, threshold가 있다. root는 교차 기준이 되는 대상을 의미하며, null인 경우 뷰포트를 의미한다.

 

  rootMargin은 root의 margin을 의미한다. margin은 상우하좌 순이기 때문에 코드의 '0px 0px 500px 0px' 의 뜻은 root(뷰포트)의 500px 아래까지도 root(뷰포트)로 간주하겠다는 의미가 된다.

 

  threshold는 대상의 일부만 보여도 대상이 교차(뷰포트 내로 들어옴)한 것으로 판단할 지를 지정하는 요소로 0.0~1.0 사이의 값을 갖는다.

 


MDN: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

생성자: https://developer.mozilla.org/ko/docs/Web/API/IntersectionObserver/IntersectionObserver

Infinity Scroll: https://googlechrome.github.io/samples/intersectionobserver/