MutationObserver API 알아보기
게시 됨: 2022-03-10복잡한 웹 앱에서는 DOM 변경이 자주 발생할 수 있습니다. 결과적으로 앱이 DOM에 대한 특정 변경에 응답해야 하는 경우가 있습니다.
한동안 DOM에 대한 변경 사항을 찾는 데 허용된 방법은 현재 사용되지 않는 Mutation Events라는 기능을 사용하는 것이었습니다. W3C에서 승인한 Mutation Events의 대체품은 MutationObserver API입니다. 이 API는 이 기사에서 자세히 설명하겠습니다.
많은 이전 기사와 참조에서 이전 기능이 대체된 이유에 대해 설명하므로 여기에서 자세히 설명하지 않겠습니다(정의를 이룰 수 없다는 사실을 제외하고). MutationObserver
API는 거의 완전한 브라우저 지원을 제공하므로 필요에 따라 모든 프로젝트는 아니지만 대부분의 프로젝트에서 안전하게 사용할 수 있습니다.
MutationObserver의 기본 구문
MutationObserver
는 이 기사의 나머지 부분에서 자세히 다루겠지만 다양한 방법으로 사용할 수 있지만 MutationObserver
의 기본 구문은 다음과 같습니다.
let observer = new MutationObserver(callback); function callback (mutations) { // do something here } observer.observe(targetNode, observerOptions);
첫 번째 줄은 MutationObserver()
생성자를 사용하여 새 MutationObserver
를 만듭니다. 생성자에 전달된 인수는 자격을 갖춘 각 DOM 변경에 대해 호출되는 콜백 함수입니다.
특정 관찰자에 대한 자격을 결정하는 방법은 위 코드의 마지막 줄을 사용하는 것입니다. 그 줄에서 MutationObserver
의 observe()
메서드를 사용하여 관찰을 시작합니다. 이것을 addEventListener()
와 비교할 수 있습니다. 리스너를 연결하는 즉시 페이지는 지정된 이벤트를 '수신'합니다. 마찬가지로 관찰을 시작하면 페이지가 지정된 MutationObserver
에 대해 '관찰'을 시작합니다.
observe()
메서드는 두 개의 인수를 사용합니다. target 은 변경 사항을 관찰할 노드 또는 노드 트리여야 합니다. 관찰자에 대한 구성을 정의할 수 있는 MutationObserverInit
개체인 options 개체가 있습니다.
MutationObserver
의 마지막 핵심 기본 기능은 disconnect()
메서드입니다. 이렇게 하면 지정된 변경 사항에 대한 관찰을 중지할 수 있으며 다음과 같습니다.
observer.disconnect();
MutationObserver를 구성하는 옵션
언급했듯이 MutationObserver
의 observe()
메서드에는 MutationObserver
를 설명하는 옵션을 지정하는 두 번째 인수가 필요합니다. 다음은 가능한 모든 속성/값 쌍이 포함된 옵션 개체의 모양입니다.
let options = { childList: true, attributes: true, characterData: false, subtree: false, attributeFilter: ['one', 'two'], attributeOldValue: false, characterDataOldValue: false };
MutationObserver
옵션을 설정할 때 이러한 행을 모두 포함할 필요는 없습니다. 단순히 참조용으로 이러한 옵션을 포함하므로 사용 가능한 옵션과 사용할 수 있는 값의 유형을 확인할 수 있습니다. 보시다시피, 하나만 제외하고 모두 부울입니다.
MutationObserver
가 작동하려면 childList
, attributes
또는 characterData
중 하나 이상이 true
로 설정되어야 합니다. 그렇지 않으면 오류가 발생합니다. 다른 네 가지 속성은 이 세 가지 중 하나와 함께 작동합니다(나중에 자세히 설명).
지금까지 개요를 제공하기 위해 구문에 대해 간단히 설명했습니다. 이러한 각 기능의 작동 방식을 고려하는 가장 좋은 방법은 다양한 옵션을 통합하는 코드 예제와 라이브 데모를 제공하는 것입니다. 이것이 이 기사의 나머지 부분에서 수행할 작업입니다.
childList를 사용하여 자식 요소의 변경 사항 관찰하기
처음으로 시작할 수 있는 가장 간단한 MutationObserver
는 추가 또는 제거할 지정된 노드(일반적으로 요소)의 자식 노드를 찾는 것입니다. 예를 들어 HTML에 순서가 지정되지 않은 목록을 만들고 이 목록 요소에서 자식 노드가 추가되거나 제거될 때마다 알고 싶습니다.
목록의 HTML은 다음과 같습니다.
<ul class="list"> <li>Apples</li> <li>Oranges</li> <li>Bananas</li> <li class="child">Peaches</li> </ul>
내 MutationObserver
용 JavaScript에는 다음이 포함됩니다.
let mList = document.getElementById('myList'), options = { childList: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation.type === 'childList') { console.log('Mutation Detected: A child node has been added or removed.'); } } } observer.observe(mList, options);
이것은 코드의 일부일 뿐입니다. 간결함을 위해 MutationObserver
API 자체를 다루는 가장 중요한 섹션을 보여드리겠습니다.
다양한 속성을 가진 MutationRecord
객체인 mutations
인수를 어떻게 반복하는지 주목하세요. 이 경우 type
속성을 읽고 브라우저가 적격한 돌연변이를 감지했음을 나타내는 메시지를 기록합니다. 또한 mList
요소(내 HTML 목록에 대한 참조)를 대상 요소(즉, 변경 사항을 관찰하려는 요소)로 전달하는 방법에 주목하십시오.
- 전체 대화형 데모 보기 →
버튼을 사용하여 MutationObserver
를 시작 및 중지합니다. 로그 메시지는 무슨 일이 일어나고 있는지 명확히 하는 데 도움이 됩니다. 코드의 주석도 일부 설명을 제공합니다.
여기에 몇 가지 중요한 사항이 있습니다.
- 콜백 함수(내가 원하는 대로 이름을 지정할 수 있음을 보여주기 위해
mCallback
이라고 명명함)는 성공적인 돌연변이가 감지될 때마다 그리고observe()
메서드가 실행된 후에 실행됩니다. - 내 예에서 자격을 갖춘 유일한 돌연변이 '유형'은
childList
이므로 MutationRecord를 반복할 때 이것을 찾는 것이 합리적입니다. 이 인스턴스에서 다른 유형을 찾는 것은 아무 일도 하지 않을 것입니다(다른 유형은 후속 데모에서 사용됩니다). -
childList
를 사용하여 대상 요소에서 텍스트 노드를 추가하거나 제거할 수 있으며 이것 역시 적합합니다. 따라서 추가되거나 제거된 요소일 필요는 없습니다. - 이 예에서는 직계 자식 노드만 자격이 있습니다. 이 기사의 뒷부분에서 이것이 어떻게 모든 자식 노드, 손자 등에 적용될 수 있는지 보여드리겠습니다.
요소 속성의 변경 사항 관찰
추적할 수 있는 또 다른 일반적인 변형 유형은 지정된 요소의 속성이 변경되는 경우입니다. 다음 대화형 데모에서는 단락 요소의 속성 변경 사항을 관찰할 것입니다.
let mPar = document.getElementById('myParagraph'), options = { attributes: true }, observer = new MutationObserver(mCallback); function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } } observer.observe(mPar, options);
- 데모 사용해보기 →
다시 한 번 명확성을 위해 코드를 축약했지만 중요한 부분은 다음과 같습니다.
-
options
객체는 내가 대상 요소의 속성에 대한 변경 사항을 찾고 싶다고MutationObserver
에 알리기 위해true
로 설정된attributes
속성을 사용하고 있습니다. - 내 루프에서 테스트 중인 돌연변이 유형은
attributes
이며 이 경우에 해당하는 유일한 유형입니다. - 또한
mutation
개체의attributeName
속성을 사용하여 어떤 속성이 변경되었는지 확인할 수 있습니다. - 관찰자를 트리거할 때 옵션과 함께 참조로 단락 요소를 전달합니다.
이 예에서 버튼은 대상 HTML 요소의 클래스 이름을 전환하는 데 사용됩니다. 돌연변이 관찰자의 콜백 함수는 클래스가 추가되거나 제거될 때마다 트리거됩니다.
문자 데이터 변경 관찰
앱에서 찾을 수 있는 또 다른 변경 사항은 문자 데이터의 변형입니다. 즉, 특정 텍스트 노드로 변경됩니다. 이는 options
객체에서 characterData
속성을 true
로 설정하여 수행됩니다. 코드는 다음과 같습니다.
let options = { characterData: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation.type === 'characterData') { // Do something here... } } }
콜백 함수에서 찾는 type
이 characterData
임을 다시 한 번 주목하세요.
- 라이브 데모 보기 →
이 예에서는 element.childNodes[0]
을 통해 대상으로 지정하는 특정 텍스트 노드에 대한 변경 사항을 찾고 있습니다. 이것은 약간 해키하지만 이 예에서는 그렇게 할 것입니다. 텍스트는 단락 요소의 contenteditable
속성을 통해 사용자가 편집할 수 있습니다.
문자 데이터 변경 사항을 관찰할 때의 과제
contenteditable
을 사용해본 적이 있다면 서식 있는 텍스트 편집을 허용하는 키보드 단축키가 있다는 것을 알고 있을 것입니다. 예를 들어 CTRL-B는 텍스트를 굵게 만들고 CTRL-I는 텍스트를 기울임꼴로 만드는 식입니다. 이렇게 하면 텍스트 노드가 여러 텍스트 노드로 분할되므로 여전히 원래 노드의 일부로 간주되는 텍스트를 편집하지 않는 한 MutationObserver
가 응답을 중지한다는 것을 알 수 있습니다.
또한 모든 텍스트를 삭제하면 MutationObserver
가 더 이상 콜백을 트리거하지 않는다는 점을 지적해야 합니다. 텍스트 노드가 사라지면 대상 요소가 더 이상 존재하지 않기 때문에 이런 일이 발생한다고 가정합니다. 이 문제를 해결하기 위해 내 데모는 텍스트가 제거될 때 관찰을 중지하지만 서식 있는 텍스트 바로 가기를 사용할 때 상황이 약간 달라집니다.
그러나 걱정하지 마십시오. 이 기사의 뒷부분에서 이러한 단점을 많이 처리하지 않고도 characterData
옵션을 사용하는 더 나은 방법에 대해 논의할 것입니다.
지정된 속성의 변경 사항 관찰
이전에 지정된 요소의 속성 변경 사항을 관찰하는 방법을 보여 드렸습니다. 이 경우 데모가 클래스 이름 변경을 트리거하더라도 지정된 요소의 속성을 변경할 수 있습니다. 그러나 다른 속성은 무시하면서 하나 이상의 특정 속성에 대한 변경 사항을 관찰하려면 어떻게 해야 합니까?
option
개체의 선택적 attributeFilter
속성을 사용하여 이를 수행할 수 있습니다. 다음은 예입니다.
let options = { attributes: true, attributeFilter: ['hidden', 'contenteditable', 'data-par'] }, observer = new MutationObserver(mCallback); function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } }
위에 표시된 것처럼 attributeFilter
속성은 내가 모니터링하려는 특정 속성의 배열을 허용합니다. 이 예에서 MutationObserver
는 하나 이상의 hidden
, contenteditable
또는 data-par
속성이 수정될 때마다 콜백을 트리거합니다.
- 라이브 데모 보기 →
다시 특정 단락 요소를 대상으로 합니다. 변경할 속성을 선택하는 선택 드롭다운을 확인하십시오. draggable
속성은 내 옵션에서 지정하지 않았기 때문에 자격이 없는 유일한 속성입니다.
어떤 속성이 변경되었는지 기록하기 위해 MutationRecord
개체의 attributeName
속성을 다시 사용하고 있음을 코드에서 확인하십시오. 물론 다른 데모와 마찬가지로 MutationObserver
는 "시작" 버튼을 클릭할 때까지 변경 사항에 대한 모니터링을 시작하지 않습니다.
여기서 지적해야 할 또 다른 사항은 이 경우 attributes
값을 true
로 설정할 필요가 없다는 것입니다. 이는 attributesFilter
가 true로 설정되기 때문에 암시됩니다. 이것이 내 옵션 개체가 다음과 같이 보일 수 있는 이유이며 동일하게 작동합니다.
let options = { attributeFilter: ['hidden', 'contenteditable', 'data-par'] }
반면에 attributeFilter
배열과 함께 attributes
을 명시적으로 false
로 설정하면 false
값이 우선하고 필터 옵션이 무시되기 때문에 작동하지 않습니다.
노드 및 해당 하위 트리의 변경 사항 관찰
지금까지 각 MutationObserver
를 설정할 때 대상 요소 자체만 다루었고 childList
의 경우 요소의 직계 자식만 처리했습니다. 그러나 다음 중 하나에 대한 변경 사항을 관찰하고 싶은 경우가 분명히 있을 수 있습니다.
- 요소 및 모든 하위 요소
- 요소 및 해당 하위 요소에 대한 하나 이상의 속성
- 요소 내부의 모든 텍스트 노드.
위의 모든 것은 options 객체의 subtree
속성을 사용하여 달성할 수 있습니다.
하위 트리가 있는 childList
먼저, 직계 자식이 아니더라도 요소의 자식 노드에 대한 변경 사항을 살펴보겠습니다. 내 옵션 개체를 다음과 같이 변경할 수 있습니다.
options = { childList: true, subtree: true }
코드의 다른 모든 것은 일부 추가 마크업 및 버튼과 함께 이전 childList
예제와 거의 동일합니다.
- 라이브 데모 보기 →
여기에는 두 개의 목록이 있으며 하나는 다른 하나 안에 중첩되어 있습니다. MutationObserver
가 시작되면 두 목록 중 하나에 대한 변경 사항에 대해 콜백이 트리거됩니다. 그러나 subtree
속성을 다시 false
(없을 때 기본값)로 변경하면 중첩 목록이 수정될 때 콜백이 실행되지 않습니다.
하위 트리가 있는 속성
다음은 또 다른 예입니다. 이번에는 attributes
및 attributeFilter
가 있는 subtree
를 사용합니다. 이를 통해 대상 요소뿐만 아니라 대상 요소의 모든 자식 요소의 속성에 대한 변경 사항을 관찰할 수 있습니다.
options = { attributes: true, attributeFilter: ['hidden', 'contenteditable', 'data-par'], subtree: true }
- 라이브 데모 보기 →
이것은 이전 속성 데모와 유사하지만 이번에는 두 개의 다른 선택 요소를 설정했습니다. 첫 번째는 대상 단락 요소의 속성을 수정하고 다른 하나는 단락 내 자식 요소의 속성을 수정합니다.
다시 말하지만, subtree
옵션을 다시 false
로 설정(또는 제거)하면 두 번째 토글 버튼이 MutationObserver
콜백을 트리거하지 않습니다. 물론 attributeFilter
를 완전히 생략할 수 있으며 MutationObserver
는 지정된 속성이 아닌 하위 트리의 속성에 대한 변경 사항 을 찾습니다.
하위 트리가 있는 문자 데이터
이전 characterData
데모에서 대상 노드가 사라지고 MutationObserver
가 더 이상 작동하지 않는 문제가 있었습니다. 이 문제를 해결하는 방법이 있지만 텍스트 노드보다 요소를 직접 대상으로 지정하는 것이 더 쉽습니다. 그런 다음 subtree
속성을 사용하여 얼마나 깊이 중첩되어 있더라도 해당 요소 내부의 모든 문자 데이터가 트리거되도록 지정합니다. MutationObserver
콜백.
이 경우 내 옵션은 다음과 같습니다.
options = { characterData: true, subtree: true }
- 라이브 데모 보기 →
관찰자를 시작한 후 CTRL-B 및 CTRL-I를 사용하여 편집 가능한 텍스트의 서식을 지정하십시오. 이전의 characterData
예제보다 훨씬 더 효과적으로 작동한다는 것을 알 수 있습니다. 이 경우 단일 텍스트 노드 대신 대상 노드 내부의 모든 노드를 관찰하기 때문에 분할된 자식 노드는 관찰자에게 영향을 주지 않습니다.
이전 값 기록
종종 DOM에 대한 변경 사항을 관찰할 때 이전 값을 기록하고 저장하거나 다른 곳에서 사용할 수 있습니다. 이것은 options
객체에서 몇 가지 다른 속성을 사용하여 수행할 수 있습니다.
속성이전 값
먼저 기존 속성 값을 변경한 후 로그아웃해 보겠습니다. 내 옵션이 내 콜백과 함께 표시되는 방식은 다음과 같습니다.
options = { attributes: true, attributeOldValue: true } function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } } }
- 라이브 데모 보기 →
MutationRecord
개체의 attributeName
및 oldValue
속성 사용에 유의하십시오. 텍스트 필드에 다른 값을 입력하여 데모를 시도하십시오. 저장된 이전 값을 반영하도록 로그가 어떻게 업데이트되는지 확인하십시오.
문자 데이터 이전 값
마찬가지로, 이전 문자 데이터를 기록하려는 경우 내 옵션이 어떻게 표시되는지는 다음과 같습니다.
options = { characterData: true, subtree: true, characterDataOldValue: true }
- 라이브 데모 보기 →
로그 메시지가 이전 값을 나타냅니다. 서식 있는 텍스트 명령을 통해 HTML을 믹스에 추가하면 상황이 약간 불안정해집니다. 이 경우 올바른 동작이 무엇인지 확실하지 않지만 요소 내부에 있는 것이 단일 텍스트 노드인 경우 더 간단합니다.
takeRecords()를 사용하여 돌연변이 가로채기
아직 언급하지 않은 MutationObserver
객체의 또 다른 메서드는 takeRecords()
입니다. 이 방법을 사용하면 콜백 함수에 의해 처리되기 전에 감지된 돌연변이를 어느 정도 가로챌 수 있습니다.
다음과 같은 줄을 사용하여 이 기능을 사용할 수 있습니다.
let myRecords = observer.takeRecords();
이것은 지정된 변수에 DOM 변경 목록을 저장합니다. 내 데모에서는 DOM을 수정하는 버튼을 클릭하자마자 이 명령을 실행하고 있습니다. 시작 및 추가/제거 버튼은 아무 것도 기록하지 않습니다. 이는 언급한 대로 DOM 변경 사항이 콜백에 의해 처리되기 전에 가로채기 때문입니다.
그러나 관찰자를 중지시키는 이벤트 리스너에서 내가 하고 있는 작업에 주목하세요.
btnStop.addEventListener('click', function () { observer.disconnect(); if (myRecords) { console.log(`${myRecords[0].target} was changed using the ${myRecords[0].type} option.`); } }, false);
보시다시피 observer.disconnect()
를 중지한 후 가로채어진 돌연변이 레코드에 액세스하고 대상 요소와 기록된 돌연변이 유형을 기록하고 있습니다. 여러 유형의 변경 사항을 관찰했다면 저장된 레코드에는 각각 고유한 유형이 있는 둘 이상의 항목이 있을 것입니다.
takeRecords()
를 호출하여 이러한 방식으로 돌연변이 레코드를 가로채면 일반적으로 콜백 함수로 전송되는 돌연변이 대기열이 비워집니다. 따라서 어떤 이유로 이러한 레코드를 처리하기 전에 가로채야 하는 경우에는 takeRecords()
가 유용할 것입니다.
단일 관찰자를 사용하여 여러 변경 사항 관찰
페이지의 서로 다른 두 노드에서 돌연변이를 찾고 있다면 동일한 관찰자를 사용하여 그렇게 할 수 있습니다. 즉, 생성자를 호출한 후 원하는 만큼 요소에 대해 observe()
메서드를 실행할 수 있습니다.
따라서 이 줄 다음에:
observer = new MutationObserver(mCallback);
그런 다음 첫 번째 인수로 다른 요소를 사용하여 여러 observe()
호출을 수행할 수 있습니다.
observer.observe(mList, options); observer.observe(mList2, options);
- 라이브 데모 보기 →
관찰자를 시작한 다음 두 목록에 대해 추가/제거 버튼을 시도하십시오. 여기서 유일한 캐치는 "중지" 버튼 중 하나를 누르면 관찰자가 대상 목록뿐만 아니라 두 목록 모두에 대한 관찰을 중지한다는 것입니다.
관찰 중인 노드 트리 이동
마지막으로 지적할 것은 MutationObserver
는 해당 노드가 상위 요소에서 제거된 후에도 지정된 노드에 대한 변경 사항을 계속 관찰한다는 것입니다.
예를 들어 다음 데모를 사용해 보세요.
- 라이브 데모 보기 →
이것은 childList
를 사용하여 대상 요소의 자식 요소에 대한 변경 사항을 모니터링하는 또 다른 예입니다. 관찰 중인 하위 목록의 연결을 끊는 버튼에 주목하십시오. "시작..." 버튼을 클릭한 다음 "이동..." 버튼을 클릭하여 중첩 목록을 이동합니다. 목록이 부모에서 제거된 후에도 MutationObserver
는 지정된 변경 사항을 계속 관찰합니다. 이런 일이 발생한다는 것은 놀라운 일이 아니지만 명심해야 할 사항입니다.
결론
이것은 MutationObserver
API의 거의 모든 주요 기능을 다룹니다. 이 심층 분석이 이 표준에 익숙해지는 데 도움이 되었기를 바랍니다. 언급했듯이 브라우저 지원은 강력하며 MDN 페이지에서 이 API에 대한 자세한 내용을 읽을 수 있습니다.
이 기사에 대한 모든 데모를 CodePen 컬렉션에 넣었습니다. 데모를 쉽게 어지럽힐 수 있는 곳을 원할 경우를 대비해.