[JS] 브라우저 감지하는 MutationObserver
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여야만 된다고
좀 더 공부를 해야할 것 같다..
참고