MutationObserver API 알아보기

게시 됨: 2022-03-10
빠른 요약 ↬ 복잡한 웹 앱 및 프레임워크에서는 DOM 변경 사항에 대한 모니터링이 때때로 필요합니다. 대화형 데모와 함께 설명을 통해 이 기사에서는 MutationObserver API를 사용하여 DOM 변경 사항을 비교적 쉽게 관찰할 수 있는 방법을 보여줍니다.

복잡한 웹 앱에서는 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 변경에 대해 호출되는 콜백 함수입니다.

특정 관찰자에 대한 자격을 결정하는 방법은 위 코드의 마지막 줄을 사용하는 것입니다. 그 줄에서 MutationObserverobserve() 메서드를 사용하여 관찰을 시작합니다. 이것을 addEventListener() 와 비교할 수 있습니다. 리스너를 연결하는 즉시 페이지는 지정된 이벤트를 '수신'합니다. 마찬가지로 관찰을 시작하면 페이지가 지정된 MutationObserver 에 대해 '관찰'을 시작합니다.

점프 후 더! 아래에서 계속 읽기 ↓

observe() 메서드는 두 개의 인수를 사용합니다. target 은 변경 사항을 관찰할 노드 또는 노드 트리여야 합니다. 관찰자에 대한 구성을 정의할 수 있는 MutationObserverInit 개체인 options 개체가 있습니다.

MutationObserver 의 마지막 핵심 기본 기능은 disconnect() 메서드입니다. 이렇게 하면 지정된 변경 사항에 대한 관찰을 중지할 수 있으며 다음과 같습니다.

 observer.disconnect();

MutationObserver를 구성하는 옵션

언급했듯이 MutationObserverobserve() 메서드에는 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... } } }

콜백 함수에서 찾는 typecharacterData 임을 다시 한 번 주목하세요.

  • 라이브 데모 보기 →

이 예에서는 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 (없을 때 기본값)로 변경하면 중첩 목록이 수정될 때 콜백이 실행되지 않습니다.

하위 트리가 있는 속성

다음은 또 다른 예입니다. 이번에는 attributesattributeFilter 가 있는 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 개체의 attributeNameoldValue 속성 사용에 유의하십시오. 텍스트 필드에 다른 값을 입력하여 데모를 시도하십시오. 저장된 이전 값을 반영하도록 로그가 어떻게 업데이트되는지 확인하십시오.

문자 데이터 이전 값

마찬가지로, 이전 문자 데이터를 기록하려는 경우 내 옵션이 어떻게 표시되는지는 다음과 같습니다.

 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 컬렉션에 넣었습니다. 데모를 쉽게 어지럽힐 수 있는 곳을 원할 경우를 대비해.