자바스크립트 MutationObserver 완벽 가이드: 실시간 다크 모드 동기화 시스템 구현

안녕하세요!

최근 웹 서비스에서 다크 모드는 선택이 아닌 필수 요소가 되었습니다. 하지만 단순히 버튼을 눌러 배경색을 바꾸는 것을 넘어, 사용자의 시스템 설정 변화나 외부 스크립트에 의한 테마 변경을 실시간으로 감지하고 대응하는 것은 또 다른 차원의 문제입니다. 오늘은 브라우저의 DOM 변화를 예리하게 관찰하는 자바스크립트 MutationObserver API를 활용하여, 웹사이트 전체의 테마를 유동적으로 관리하고 동기화하는 고급 기법을 다뤄보려고 합니다. 브라우저가 스스로 변화를 감지하게 만드는 이 매력적인 기술을 통해, 사용자 경험을 한 단계 더 끌어올리는 최적화 로직을 함께 파헤쳐 보시죠.


1. 단순한 테마 변경을 넘어 ‘동기화’가 필요한 이유

우리가 흔히 접하는 다크 모드 구현 방식은 버튼 클릭 시 body 태그에 클래스를 추가하는 정도에 그칩니다. 하지만 실제 서비스 환경에서는 운영체제의 테마 설정(System Preference)이 바뀌거나, 다국어 처리 과정에서 특정 요소의 속성이 변할 때 테마가 깨지는 현상이 빈번히 발생하곤 합니다.

저는 이전에 [CSS 변수와 Math 객체로 구현하는 자바스크립트 아날로그 시계] 프로젝트를 진행하면서, 자바스크립트로 스타일의 원천인 CSS 변수를 직접 제어하는 것이 얼마나 강력한지 경험했습니다. 이번에도 그 경험을 살려 CSS 변수를 활용하되, “언제” 그 변수를 바꿀 것인가를 고민하게 되었죠. 수동으로 매번 함수를 호출하는 대신, 브라우저가 DOM의 변화를 보고 있다가 알아서 테마를 맞춰주는 시스템을 구축하고 싶었습니다. 이것이 바로 우리가 자바스크립트 MutationObserver에 주목해야 하는 이유입니다.

자바스크립트 MutationObserver 처음 화면

2. 브라우저의 파수꾼, MutationObserver API의 이해

MutationObserver는 이름 그대로 DOM 트리에서 발생하는 구체적인 변화(Mutation)를 관찰(Observe)하는 도구입니다. 기존에 사용되던 MutationEvents는 성능 저하의 주범이었지만, 최신 스택인 이 API는 비동기적으로 작동하여 브라우저의 부하를 최소화합니다.

특히 다크 모드 동기화 시스템에서는 html이나 body 태그의 class 혹은 data-theme 속성이 변하는 시점을 포착하는 것이 핵심입니다. 제가 처음 이 기술을 접했을 때 가장 놀랐던 점은, 외부 광고 스크립트나 브라우저 확장 프로그램이 강제로 스타일을 바꿀 때조차 이를 놓치지 않고 낚아챌 수 있다는 것이었습니다. 이는 단순히 click 이벤트를 감시하는 것과는 비교할 수 없는 정교함을 제공합니다.

3. 핵심 로직: DOM의 변화를 가로채는 코드 설계

이제 본격적으로 관찰자를 세워볼 시간입니다. 제가 이번 자바스크립트 MutationObserver 시스템을 설계하며 가장 중요하게 생각한 것은 “필요한 변화만 골라내는 능력”이었습니다. 전체 DOM을 다 감시하면 성능이 떨어지기 때문에, 특정 타겟의 속성 변화만 타격하는 정교한 옵션 설정이 필요했죠.

자바스크립트 MutationObserver의 핵심 코드를 구현하면서 느꼈던 점은, attributeFilter를 사용하는 디테일이 성능 최적화의 핵심이라는 것이었습니다. 무분별한 감시를 방지하는 이 기법은 이전에 [인피니트 스크롤 구현하기]에서 Intersection Observer를 사용해 뷰포트 교차 지점만 정교하게 감시했던 최적화 마인드와도 일맥상통합니다.

4. CSS 변수(var)와 자바스크립트의 유기적인 결합

관찰자가 변화를 감지했다면, 이제 화면을 바꿀 차례입니다. 저는 색상값을 하드코딩하지 않고 CSS 변수를 활용하는 방식을 택했습니다. 자바스크립트는 단지 –bg-color와 같은 변수의 값을 동적으로 갈아끼우는 역할만 수행하는 것이죠.

이 방식은 유지보수 면에서 엄청난 장점을 가집니다. 디자이너가 색상을 바꾸고 싶어 할 때 자바스크립트 로직을 건드릴 필요 없이 CSS 변수 정의만 수정하면 되니까요. 예전에 [객체 매핑 설계로 코드 줄이기] 포스팅에서 다뤘던 ‘관심사 분리’의 원칙을 테마 관리 시스템에도 적용해 본 셈입니다.

5. 자바스크립트 MutationObserver 실행

이론적인 설명만으로는 감시자가 어떻게 작동하는지 피부로 느끼기 어려울 수 있습니다. 그래서 제가 직접 여러분이 지켜볼 수 있는 실습 공간을 마련했습니다. 아래 버튼을 눌러보세요. 여러분이 속성을 강제로 바꾸는 순간, 제가 세워둔 자바스크립트 MutationObserver가 이를 얼마나 기민하게 낚아채서 화면을 변화시키는지 실시간으로 확인하실 수 있습니다!

다크 모드 감시 시스템

현재 상태: 라이트 모드

Observer가 html 태그의
[data-theme] 속성을 감시 중입니다.

6. 시행착오: 무한 루프의 늪과 성능 최적화

방금 실습에서 보신 것처럼 속성 변화는 감지했지만, 실제 서비스에 적용할 때는 조심해야 할 함정이 있습니다. 자바스크립트 MutationObserver를 구현하는 과정에서 예상치 못한 복병을 만났습니다. 만약 콜백 함수 내부에서 다시 속성을 수정하게 되면, 그 수정이 다시 Observer를 호출하여 무한 루프에 빠지는 현상이 생기더군요. 이 시행착오를 통해 저는 observer.disconnect()와 re-observe 타이밍을 조절하거나, 현재 값과 변경될 값을 비교하는 방어 로직이 얼마나 중요한지 깨달았습니다.

또한, 시스템 설정 변화(prefers-color-scheme)와 MutationObserver를 어떻게 조화시킬지도 큰 고민이었습니다. 결국 시스템 변화를 감지해 DOM 속성을 먼저 바꾸고, 그 속성 변화를 Observer가 감지해 최종 스타일을 적용하는 ‘단방향 데이터 흐름’을 설계함으로써 문제를 해결했습니다. 이는 [바닐라 JS 상태 관리 시스템]에서 다뤘던 리액티브 프로그래밍의 철학과도 닮아 있어 큰 보람을 느꼈습니다.


오늘 다룬 자바스크립트 MutationObserver는 단순히 기능을 구현하는 도구를 넘어, 브라우저와 웹사이트가 어떻게 유기적으로 대화하는지를 보여주는 훌륭한 창구입니다. 변화를 수동적으로 기다리는 코드가 아니라, 변화를 능동적으로 관찰하고 반응하는 코드를 짜는 과정이 여러분에게도 즐거운 자극이 되었기를 바랍니다.

단순한 다크 모드 구현을 넘어, 서비스의 안정성과 확장성까지 고려하는 이런 디테일한 접근이 결국 ‘좋은 개발자’를 만든다고 믿습니다. 여러분의 프로젝트에도 이 파수꾼을 한 번 세워보시는 건 어떨까요?

구현 중에 옵션 설정이 헷갈리거나 무한 루프 문제로 골머리를 앓고 계신다면 언제든 댓글 남겨주세요.

댓글 남기기