Intersection Observer API로 뷰포트에 반응하는 인터랙션 쉽게 구현하기

Explanation

최근에 여차여차하다가 Intersection Observer API를 사용할 일이 생겼는데요. 그런데 제가 Intersection Observer를 처음 봐서.. 약간 애먹었답니다. 그래서 오늘은 간단한 예시를 통해서 짧게나마 Intersection Observer API에 대해서 알아보려 합니다.

샘플 코드는 Vite, React를 사용해서 만들었고 https://github.com/falsy/blog-post-example/tree/main/intersection-observer-api에서 확인하실 수 있습니다.

1. Intersection Observer API

Intersection Observer API는 23년 10월에 W3C의 초안이 나왔어요. 비교적 최근이죠?! 아직 표준화는 단계를 밟고 있지만, 이미 대부분의 브라우저가 지원하기 때문에 충분히 유용하게 사용할 수 있답니다.
(저는 처음봤지만, 민망하게도 브라우저들이 지원한지는 꽤 오래되었다는 사실은 안비밀.. (2016 – 2019))

[W3C 초안]
https://www.w3.org/TR/2023/WD-intersection-observer-20231018/

[브라우저 지원]
– Chromium: Shipped in Chrome 51
– Edge: Shipped in build 14986
– Firefox: Shipped in Firefox 55
– WebKit: Shipped in Safari 12.1 and iOS 12.2
(https://github.com/w3c/IntersectionObserver)

Intersection Observer라는 이름처럼 ‘교차 관찰자’인데요. 상위 요소나 또는 최상위 문서의 뷰포트와 대상 요소 사이의 변화를 비동기적으로 관찰할 수 있는 수단을 제공하는데요. 아주 간단한 예를 들면, 웹 페이지의 스크롤에 따라서 이미지나 컨텐츠를 지연 로딩(Lazy-loading)하는 것과 같은 작업을 쉽게 구현할 수 있답니다.

그 밖에도 웹 페이지에 광고를 넣었을때, 광고 영역이 사용자에게 얼마나 보여졌는지 확인하거나, 무한 스크롤을 구현한다거나, 또는 사용자에게 대상 요소가 보여졌을 때에 대한 인터랙티브나 애니메이션을 수행할 지를 여부를 제어할 수 있습니다.

2. threshold

설명은 이 정도로 하고, 바로 샘플 코드를 통해서 동작을 확인해 볼게요. IntersectionObserver는 ‘root’, ‘threshold’, ‘rootMargin’ 이렇게 크게 세 가지의 옵션을 제공하는데요. 그 중에 threshold을 먼저 알아보면,

우선, 저는 위와 같이 대상 요소를 컴포넌트로 정의 했는데요. IntersectionObserver 라는 객체를 사용하며 observe 메서드를 통해 대상을 관찰하고 unobserve 메서드를 통해서 관찰을 종료할 수 있습니다.

바로 데모의 동작을 보면 간단한데요. threshold 값이 1 이라면 대상 요소가 뷰포트에 전부다 노출 되었을 때 활성화 되는 것이고 0.5 라면 50% 이상 뷰포트에 노출 되었을 때, 0 이라면 아주 조금이라도 뷰포트에 노출 되었다면 활성화됩니다.

[데모]

위 예시에서는 root 옵션을 설정하지 않았는데, root 옵션의 기본값은 null로 null은 브라우저의 뷰포트를 기준으로 동작합니다.

위 데모에서는 실제 스크롤되는 컨테이너 영역을 통해 대상이 다 보여지는 시점과 브라우저의 뷰포트에서 대상이 다 보여지는 시점이 같아서 root를 설정해주지 않았어도 동일하게 동작합니다.

3. rootMargin

다음으로 rootMargin 옵션은 이름 그대로 root의 여백 영역에 대한 설정인데요. 저는 여기에서 조금 헷갈렸었어요. 입력할 수 있는 값은 문자열로 CSS 속성 값과 비슷하게 동작합니다. 예를 들어 “100px”으로 설정하면 “상하좌우 100px” 풀어쓰면 “100px 100px 100px 100px”으로 동작합니다. 그리고 “100px 0″으로 설정하면 “상하 100px”, “좌우 0px”으로 동작합니다. 그리고 기본값은 “0px 0px 0px 0px”로 root 요소의 수정되지 않는 사각형의 경계로 설정됩니다.

[깨알 팁]
React 19부터 forwardRef를 사용하지 않고도 부모의 ref 값을, ref라는 이름의 Props로 받을 수 있다는 사실!!

위와 같이 예시 코드를 만들어 봤는데요. 이번엔 root의 여백값이 요소의 컨테이너 영역에 설정되야 하기 때문에 부모 컴포넌트로부터 ref로 받아서 설정해 주었습니다.

위 예시에서 rootMargin는 상단에만 100px을 주었으니 컨테이너의 위쪽 영역에 100px 만큼 공간이 추가되었다고 생각하면 되는데요. 대상 요소가 스크롤을 내려서 나타날 때는 차이가 없고 더 스크롤이 내려서 위로 사라질때 위에 100px의 영역이 더 있는 것이기 때문에, threshold 값이 1 이지만 바로 비활성화 되지 않고 rootMargin로 설정한 100px 이상 더 스크롤 되었을 때 비활성화 됩니다.

이게 글로 말하려니 더 어렵네요..

간단하게 이미지로 표현해보면 위와 같은데요. 만약에, 저 분홍이가 대상이고 분홍이는 현재 threshold 값이 0이기 때문에 조금이라도 뷰포트에 노출되면 활성화 되는데요. 위와 같이 rootMargin 값이 “40px 40px 40px 40px”으로 설정되어 있다면(옥색? 영역) 아래쪽에도 40px의 여백이 있는 것이고 현재 분홍이의 위치는 그 영역에 포함되기 때문에 분홍이는 활성화 된답니다.

그리고 그밖에 몇가지 더 예를 들면 아래와 같습니다.

[데모]

4. 마무리

이글은 아주 간단하게 Intersection Observer API에 대해서 알아보는 글이기에 조금 더 자세하고 다양한 활용에 대해서는 MDN 글을 참고하면 더 좋을 것 같습니다!
MDN: https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API