프론트/Javascript

[JS] 브라우저 감지하는 MutationObserver

척척박사또라에몽 2022. 7. 4. 13:04

DOM에 ref 걸어놓고 감지되는 것을 감지해서 css를 activate 시키는 기능을 추가 시켜야했다

브라우저 감지 인터페이스는 JS에 여러가지가 있는데

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

오늘은 MutationObserver를 정리해보려고 한다.

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

 

MutationObserver - Web APIs | MDN

The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature, which was part of the DOM3 Events specification.

developer.mozilla.org

기본적인 설명이 잘 써있다

const targetNode = document.getElementById('some-id');

// Options for the observer (which mutations to observe)
const config = { 
	attributes: true, // 속성 변화 할때 감지 
	childList: true, // 자식노드 추가 / 제거 감지
	subtree: true // 자식뿐만 아니라 손자 이후로도 모두 감지
};

// Callback function to execute when mutations are observed
const callback = function(mutationList, observer) {
    // Use traditional 'for loops' for IE 11
    for(const mutation of mutationList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

// Later, you can stop observing
observer.disconnect();
속성 설명
childList true/false 대상 노드 하위 요소 추가 제거 감지
attributes true/false 대상 노드 속성 변화 감지
charactorData true/false 대상 노드 데이터 변화 감지
subTree true/false 손자까지 감지
attributeOldValue true/false 대상 노드 데이터 변경 전 내용 기록
attributeFilter ["A","B"] 모든 속성 관찰할 필요 없는 경우 속성 로컬 이름의 배열로 설정

변경 감지 대상할 엘리먼트를 선정할때는 변경되는 자식의 가장 상위 요소를 골라야 변화 감지를 제대로 한다

 

리액트 예제를 찾아봤는데 이 친구 하나밖에 없었다 흑흑

하지만 하라는 대로 하니까 안된다

https://www.30secondsofcode.org/react/s/use-mutation-observer

 

React useMutationObserver hook - 30 seconds of code

Watches for changes made to the DOM tree, using a MutationObserver

www.30secondsofcode.org

import styled from '@emotion/styled';
import React, {forwardRef, useEffect, useReducer, useRef, useState} from 'react';

const useMutationObserver = (
    ref,
    callback,
    options = {
      attributes: true,
      characterData: true,
      childList: true,
      subtree: true,
    }
) => {
  React.useEffect(() => {
    if (ref.current) {
      const observer = new MutationObserver(callback);
      observer.observe(ref.current, options);
      return () => observer.disconnect();
    }
  }, [callback, options]);
};
export default function App () {
  const mutationRef = useRef();
  const incrementMutationCount = (mutationList) =>{
    console.log('mutationList:',mutationList)
    setMutationCount(mutationCount + 1);
  }
  const [mutationCount, setMutationCount] = useState(0);
  useMutationObserver(mutationRef,incrementMutationCount);
  const [content,setContent] = useState('hello world');

  return(
      <>
        <label htmlFor="content-input">Edit this to update the text:</label>
        <textarea
            id="content-input"
            style={{width: '100%'}}
            value={content}
            onChange={e => setContent(e.target.value)}
        />
        <div
            style={{ width: '100%' }}
            ref={mutationRef}
        >
          <div
              style={{
                resize: 'both',
                overflow: 'auto',
                maxWidth: '100%',
                border: '1px solid black',
              }}
          >
            <h2>Resize or change the content:</h2>
            <p>{content}</p>
          </div>
        </div>
        <div>
          <h3>Mutation count {mutationCount}</h3>
        </div>
      </>
  )
}

원래 코드대로라면 Mutation count 0에서 숫자가 올라가야한다

하지만 안올라간다

보니까

const useMutationObserver = (
    ref,
    callback,
    options = {
      attributes: true,
      characterData: true,
      childList: true,
      subtree: true,
    }
) => {
	useEffect(() => {
        if (ref.current) {
          const observer = new MutationObserver(callback);
          observer.observe(ref.current, options);
          return () => observer.disconnect();
        }
  }, [callback, options]);
};

useMutationObserver를 hook화 시키면서 return () => observer.disconnect()가 실행이 돼

callback을 제대로 안탄다

const useMutationObserver = (
    ref,
    callback,
    options = {
      attributes: true,
      characterData: true,
      childList: true,
      subtree: true,
    }
) => {
  useEffect(() => {
    if (ref.current) {
      const observer = new MutationObserver(callback);
      observer.observe(ref.current, options);
      return observer;
    }
  }, [callback, options]);
};
export default function App () {
  const mutationRef = useRef();
  const incrementMutationCount = (mutationList) => setMutationCount(mutationCount + 1);
  const [mutationCount, setMutationCount] = useState(0);
  const observer = useMutationObserver(mutationRef,incrementMutationCount);
  const [content,setContent] = useState('hello world');
  useEffect(()=>{
    return () => observer.disconnect;
  },[])

그래서 observer를 return으로 넘겨서 main component의 useEffect() 에서 disconnect를 실행시켜줬더니

잘된다!!

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

type : 변경사항의 타입

 attributes : 속성이 변경

 charactorData : 데이터 변경

 childList : 자식 노드가 추가되거나 제거됨

target : 변경 사항이 발생한 타켓 노드

addedNodes/removeNodes : 추가되거나 제거된 노드

previousSibling/nextSibling : 추가되거나 제거된 노드의 previous/next 형제의 노드

attributeName/attributeNamespace : 변경된 속성의 이름

oldValue: 변경된 이전의 값 속성이나 텍스트가 변하면 attributeOldValue/ characterDataOldValue 옵션 줬을때 받아올수있다

 

참고로... react에서 그냥

<div id='hello'></div>

해서 하면 안된다.... 왤까? HTMLElement라서 안된다고 한다 Node여야만 된다고

좀 더 공부를 해야할 것 같다..

 

참고

- https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-MutationObserver-DOM%EC%9D%98-%EB%B3%80%ED%99%94%EB%A5%BC-%EA%B0%90%EC%8B%9C#MutationObserver_%EC%98%B5%EC%85%98