Intersection Observer 란?

하얀족제비·2021년 8월 14일
14

🤔 학습이유


기존에 clientHeight, scrollTop, scrollHeight를 활용하여 무한 스크롤을 구현해봤었기 때문에 이번에는 Intersection Observer라는 것을 활용하여 접근해보려고 한다.

무려 debounce나 throttle를 사용하지 않고도 브라우저에 부담을 가하지 않고 무한 스크롤을 간편하게 구현 할 수 있다고 한다.

🧐 Intersection Observer


Intersection Observer(교차 관찰자 API)는 타겟 엘레멘트와 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API이다.

(여기서 뷰포트란, 현재 화면에 보여지고 있는 다각형의 영역을 말한다. 한 마디로 그냥 화면!!)

즉, Intersection Observer란 화면(뷰포트) 상에 내가 지정한 타겟 엘레멘트가 보이고 있는지를 관찰하는 API이다.

이 기능은 비동기적으로 실행되기 때문에, scroll 이벤트 기반의 요소 관찰에서 발생하는 렌더링 성능이나 이벤트 연속 호출 같은 문제 없이 사용할 수 있다.

😮 활용


  • 페이지 스크롤 시 이미지를 Lazy Loading할 때
  • Infinite Scrolling 구현할 때
  • 사용자가 결과를 볼 것인지에 따라 애니메이션 동작 여부를 결정할 때

나는 여기서 2번째인 무한 스크롤에 적용해 볼 생각이다.

😕 왜 사용할까?


1. 호출 수 제한 방법으로 debounce, throttle을 사용하지 않아도 된다.

앞서 언급했지만, 스크롤 이벤트를 달게 되면 굉장히 많은 호출이 일어나게 된다.

window.addEventListener('scroll', function() {
	return console.log('scroll!');
}

이렇게 하면 엄청 많은 콘솔로그가 찍히는 걸 확인해 볼 수 있다.

이를 컨트롤 하기 위해서 debounce, throttle 기법을 사용하는데,

Intersection Observer를 사용하면 훨씬 간편하게 해결할 수 있다.

2. reflow를 하지 않는다.

새로 알게 된 사실인데

스크롤 이벤트에서는 현재의 높이 값을 알기 위해 offsetTop을 사용하는데 정확한 값을 가져오기 위해 매번 layout을 새로 그리게 된다고 한다.

즉 렌더 트리를 다시 그린다는 것인데,

reflow라고도 불리우는 이 일련의 과정이 반복되면 당연히 브라우저의 성능이 저하되고 화면의 버벅거림이 생길 수 밖에 없다.

3. 호환성

IE를 제외하면 대부분의 브라우저에서 지원되는 것으로 확인했다.

🤩 사용법


1. Parameters

const io = new IntersectionObserver (callback, options); // 관찰자 초기화
io.observe(element)

new IntersectionObserver()를 통해 생성한 인스턴스(io)로 관찰자를 초기화하고 관찰할 대상(Element)를 지정한다.

Callback : 관찰할 대상이 등록되거나 가시성(보이는지 안보이는지)에 변화가 생기면 관찰자는 콜백(Callback)을 실행한다.

Callback은 2개의 인수(entries, observer)를 가진다.

entries

IntersectionObserverEntry 인스턴스의 배열이다. IntersectionObserverEntry는 읽기 전용의 다음 속성을 갖는다.

  • boundingClientRect: 관찰 대상의 사각형 정보
  • intersectionRect: 관찰 대상의 교차한 영역 정보
  • intersectionRatio: 관찰 대상의 교차한 영역 백분율
  • isIntersecting: 관찰 대상의 교차 상태(Boolean, 주로 사용하는 속성!)
  • rootBounds: 지정한 루트 요소의 사각형 정보
  • target: 관찰 대상 요소
  • time: 변경이 발생한 시간 정보

코드로 자세히 살펴보면

...
<body>
  <div id="element">관찰 요소</div>

  <script>
    const Element = document.querySelector('#element')
    const options = {}
		// observer: IntersectionObserver instance
    const io = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          console.log(entry)
        })
      }, options)

io.observe(Element)
  </script>
</body>
...

간단하게 '관찰 요소'라고 적혀있는 태그를 관찰하는 건데, 이렇게 작성하고 콘솔 창을 보면

이렇게 나온다.

하나하나 다시 살펴보면

  • boundingClientRect
    • 관찰 대상의 사각형 정보를 반환한다.
    • 이 값은, Element.getBoundingClientRect()를 사용해 동일하게 얻을 수 있다.

  • intersectionRect
    • 관찰 대상과 루트 요소와의 교차하는 영역에 대한 사각형 정보이다.

  • intersectionRatio

    • 관찰 대상이 루트 요소와 얼마나 교차하는지의 수치를 0~1.0 사이의 숫자로 반환한 것이다.
    • 이는 intersectionRect영역과 boundingClientRect 영역의 비율을 의미한다.
  • isIntersecting

    • 관찰 대상이 루트 요소와 교차 상태로 들어가거나 교차 상태에서 나가는지 여부를 나타내느 값(Boolean)이다.
    • 루트 요소와 교차했는지에 따라 결정 되는 Boolean값으로 무한 스크롤을 구현할 수 있다. (맨 마지막 요소가 보이면 api 통신하도록 callback 함수 설계)

  • rootBounds

    • 루트 요소에 대한 사각형 정보를 반환한다.
    • 이는 옵션 rootMargin에 의해 값이 변경되며, 만약 별도의 루트 요소(옵션 root)를 선언하지 않았을 경우 null을 반환한다.
  • target

    • 관찰 대상(Element)을 반환한다.
  • time

    • 문서가 작성된 시간을 기준으로 교차 상태 변경이 발생한 시간을 나타내는 DOMHighResTimeStamp를 반환한다.

✨ Observer

  • 콜백 함수가 호출되는 IntersectionObserver

✨ Options

options는 관찰이 시작되는 상황에 대해 옵션을 설정할 수 있다. 기본 값이 정해져 있으므로 필수는 아니다.

  • root
    • 교차 기준이 되는 엘리먼트. (default: null)
    • 타겟의 가시성을 검사하기 위해 부표트 대신 사용할 요소 객체를 지정한다.
    • 타겟의 조상 요소이어야 하며 지정하지 않거나 null일 경우 브라우저의 뷰포트가 기본 사용된다.

  • rootMargin
    • root로 지정된 엘리먼트의 margin값 설정(default: 0px)
    • 바깥 여백을 이용해 Root 범위를 확정, 축소가 가능하다.
    • css와 같이 4단계로 여백 설정 가능하며 px 또는 %로 나타낸다.

  • thredhold
    • root엘리먼트와 observer 엘리먼트가 얼만큼 교차되어있는지를 말하며 0은 전혀 교차 x, 1은 전체가 교차됨을 의미한다.(default: 0)
    • 옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시
    • 기본값은 Array 타입의 [0]이지만 Number 타입의 단일 값으로도 작성 가능
    • 0: 타겟의 가장자리 픽셀이 Root 범위를 교차하는 순간 옵저버가 실행
    • 0.3: 타겟의 가시성 30% 일 때 옵저버 실행
    • [0, 0.3, 1]: 타겟의 가시성이 0%, 30%, 100% 일 때 모두 옵저버 실행

코드 작성 예시)

new IntersectionObserver ((entries, observer) => {
	//...
}, {rootMargin: 100px, thredhold: 0.5 });

// 별도로 분리하여 작성할 수도 있다.
new IntersectionObserver (_onIntersection, options);

const _onIntersection = (entires, observer) => {
	//...
}

const options = { rootMargin: 100px, htredhold: 0.5 }

2. Methods

  • IntersectionObserver.observe(target): 관찰 시작
const io1 = new IntersectionObserver(callback, options)
const io2 = new IntersectionObserver(callback, options)

const div = document.querySelector('div')
const li = document.querySelector('li')
const h2 = document.querySelector('h2')

io1.observe(div) // DIV 요소 관찰
io2.observe(li) // LI 요소 관찰
io2.observe(h2) // h2 요소 관찰
  • IntersectionObserver.unobserve(target): 관찰 종료
const io1 = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    // 가시성의 변화가 있으면 관찰 대상 전체에 대한 콜백이 실행되므로,
    // 관찰 대상의 교차 상태가 false일(보이지 않는) 경우 실행하지 않음.
    if (!entry.isIntersecting) {
      return
    }
    // 관찰 대상의 교차 상태가 true일(보이는) 경우 실행.
    // ...

    // 위 실행을 처리하고(1회) 관찰 중지
    observer.unobserve(entry.target)
  })
}, options)
  • IntersectionObserver.disconnect(target): 모든 요소 관찰 멈추기
const io1 = new IntersectionObserver(callback, options)
const io2 = new IntersectionObserver(callback, options)

// ...

io1.observe(div)
io2.observe(li)
io2.observe(h2)

io2.disconnect() // io2가 관찰하는 모든 요소(LI, H2) 관찰 중지

이름이 매우 직관적이어서 편한 것 같다.

메소드는 이외에도 많이 존재한다.

코드 예시

<!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>
  <style>
    #block {
      height: 1000px;
    }
    .box {
      background-color: black;
    }
  </style>
</head>
<body>
  <div id="block"></div>
    <div id="element">관찰 요소</div>
  <script>
    const Element1 = document.querySelector('#element')
    const block = document.querySelector('#block')
    const options = {threhold: 0.5}
    const io = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
          console.log(observer)
          if (entry.isIntersecting) {
            block.classList.add('box')
          }
        })
      }, options)

    io.observe(Element1)
  </script>
</body>
</html>

관찰요소가 뷰포트에 들어오는 순간

id가 block인 요소의 배경색을 검정색으로 바꿔주는 코드이다.

처음에는 아무것도 없는 하얀색 배경이었다가

이렇게 관찰요소를 발견하게 되면 검정색으로 바뀐다.

이런 식으로 특정 요소에 IntersectionObserver를 달아두고

관찰하게 되면 콜백함수로 내가 원하는 로직을 구현하도록 짜면 된다.

무한스크롤도 마찬가지이다. 코드가 복잡해서 따로 첨부하진 않았지만

맨 마지막 요소가 관찰되는 순간

새로운 데이터를 불러오면 되는 것이다.

profile
안녕하세요~ 개발을 꿈꾸는 하얀족제비입니다!

0개의 댓글