DOM 변화 감지하기 - MutationObserver & ResizeObserver

ggong·2021년 10월 13일
3

얼마 전 프로젝트에 Testing-Library를 적용하는 과정에서 MutationObserver is not a constructor라는 에러가 계속 났다. (에러 원인은 버전 충돌이었음)
삽질을 오래 했는데, 덕분에 애매하게 알았던 MutationObserver를 좀 자세히 찾아볼 수 있었다.
그래서 오늘은 DOM의 변경을 감지하는 MutationObserver와 ResizeObserver에 대해서 남겨두려고 한다.

브라우저에서 DOM에 어떤 변화가 발생하면, 이 변화를 감지해 특정 이벤트를 발생시켜야 하는 경우가 있다. 자바스크립트에서는 DOM의 변경을 쉽게 감지하기 위해 5개의 내장 인터페이스들을 제공한다.

  • IntersectionObserver : 루트 영역(뷰포트)와 대상 객체의 겹침을 감시
  • MutationObserver : 객체의 속성 변경을 감시
  • PerformanceObserver : 프로세스 성능 모니터링
  • ReportingObserver : 웹 사이트의 표준 및 정책 준수 현황을 감시
  • ResizeObserver : 객체의 너비, 높이의 변화를 감시

MutationObserver

이 중에서 MutationObserver는 DOM의 속성, 텍스트, 자식 노드들에 대한 변경을 감지할 수 있는 API이다.
특정 노드 객체를 관찰하고, 변경이 발생했을 때 콜백 함수를 실행한다.

const observer = new MutationObserver(callback);

const callback = (mutationList, observer) => {
  console.log(mutationList);
};

이렇게 MutationObserver constructor를 통해 생성 가능하고, 콜백을 필수로 전달해 줘야 한다.
observer 객체를 생성한 후 추적을 원하는 DOM node와 config를 연결해준다.
config에는 감지하기를 원하는 변경 속성들을 boolean 형태로 지정할 수 있다. (옵셔널임)

MutationObserver config에 들어갈 수 있는 옵션들

  • childList : 자식 노드에 발생하는 변경사항
  • subtree : 하위 모든 노드들의 변경사항
  • attributes : 해당 노드의 attribute 속성
  • attributeFilter : 추적을 원하는 attribute name들을 배열 형태로도 전달 가능
  • characterData : node.data에서 발생하는 변경사항 (text content)
  • attributeOldValue : true이면 변경되기 이전과 이후의 attribute value를 콜백에서 모두 전달받을 수 있다. false인 경우 새로운 value만 콜백에 전달된다.
  • characterDataOldValue : true이면 변경되기 이전과 이후의 node.data를 콜백에서 모두 전달받을 수 있다. false인 경우 새로운 값만 콜백에 전달된다.

이렇게 특정 노드를 추적하기 시작하면, 해당 노드에 지정한 변경사항이 발생할 때마다 콜백이 실행된다.

const el = document.getElementById('root');

observer.observe(el, {
  childList: true,
  subtree: true,
  attributeOldValue: true,
});

콜백 함수는 첫번째 파라미터로 변경 사항을 표현하는 MutationRecord 객체 형태의 배열을 받는다.

const callback = (mutationList, observer) => {
  console.log(mutationList, observer);
};

// mutationList를 찍어보면...
mutationList = [{
  type: "childList",
  target: <div#elem>,
  removedNodes: [<b>],
  nextSibling: <text node>,
  oldValue: null,
  previousSibling: <text node>
  // other properties empty
}, {
  type: "characterData"
  target: <text node>
}];

첫번째 파라미터인 MutationRecord 배열에는 변경 사항에 대한 정보가 담긴다. MutationRecord 객체는 아래와 같은 속성을 가진다.

MutationRecord에서 표현되는 속성들

  • type : 변경사항의 타입. 'attributes', 'characterData', childList' 중 하나의 값을 가진다.
    "attributes" - 속성이 변경됨
    "characterData" - 데이터 변경시 (text node)
    "childList" - 자식 노드가 추가되거나 제거됨
  • target : 변경사항이 발생한 타겟 노드
  • addedNodes/removedNodes : 추가되거나 제거된 노드
  • previousSibling/nextSibling : 추가되거나 제거된 노드의 previous/next 형제 노드
  • attributeName/attributeNamespace : 변경된 속성의 이름
  • oldValue : 변경 이전의 값. 속성이나 텍스트가 변경되었을 경우, attributeOldValue/characterDataOldValue 옵션을 주었을 경우에 받아올 수 있다.

그리고 콜백함수의 두번째 파라미터로는 observer 객체 자신을 받을 수 있다.

변경사항 추적이 끝나면, DOM 변경 알림을 받는 MutationObserver 인스턴스를 중지해줘야 한다.
제공되는 disconnect 함수를 통해 변경 추적을 중지할 수 있다.

observer.disconnect();

ResizeObserver

MutationObserver와 비슷하게, ResizeObserver는 DOM 객체의 크기 변화를 감지한다. 정확히는 element의 border box, content box, SVGelement의 bounding box의 변화를 감지한다.

인스턴스 생성 후 콜백 함수를 넘겨주고 observe()를 실행하면 추적이 시작된다.

const observer = new ResizeObserver(callback);
const el = document.getElementById('root');

const callback = (entries, observer) => {
  entries.forEach(entry => {
    console.log(entry);
  }

observer.observe(el, options);

ResizeObserver의 options에 들어갈 수 있는 속성은 box 하나만 있다. 변경사항을 추적할 css box model을 설정하는 옵션으로, 아래 중 하나의 값이 들어갈 수 있다.

  • content-box (기본값)
  • border-box
  • device-pixel-content-box

그러면 콜백 함수는 설정한 요소의 크기가 변경될 때마다 ResizeObserverEntry 인스턴스의 배열과, observer 자신을 파라미터로 받을 수 있다.

ResizeObserverEntry 인스턴스의 배열에 포함되는 속성들

  • contentRect(legacy) : 관찰 대상의 사각형 정보(DOMRectReadOnly)
  • target(legacy) : 관찰 대상 요소(Element)
  • contentBoxSize : 관찰 대상의 content-box(content) 크기
  • borderBoxSize : 관찰 대상의 border-box(content + padding + border) 크기

ResizeObserver는 IE는 지원하지 않기 때문에 실제 프로젝트에 사용하려면 폴리필 적용이 필요하다.
@juggle/resize-observer, resize-observer-polyfill와 같은 폴리필이 소개되는 듯.
디바이스의 너비가 일정 픽셀 이하로 줄어들었을 때 콜백 함수를 활용하거나, 엘리먼트의 크기 변화를 감지해야 하는 경우에 유용하게 사용할 수 있다.



참고:
MDN
(https://developer.mozilla.org/ko/docs/Web/API/MutationObserver#mutationobserverinit)
MutationObserver
(https://ko.javascript.info/mutation-observer)
Resize Observer - 요소의 크기 변화 관찰
(https://heropy.blog/2019/11/30/resize-observer/)

profile
파닥파닥 FE 개발자의 기록용 블로그

0개의 댓글