Сегодня, в переводе десятого материала из серии, посвящённой особенностям работы механизмов JavaScript, мы расскажем о том, как отслеживать изменения в DOM с помощью API MutationObserver.
Клиентские части веб-приложений становятся всё сложнее, требуют всё больше системных ресурсов. Происходит это по разным причинам, в частности из-за того, что таким приложениям нужны продвинутые интерфейсы, благодаря которым раскрываются их возможности, и из-за того, что им приходится выполнять сложные вычисления на стороне клиента.
Всё это ведёт к усложнению задачи контроля состояния интерфейсов приложений в процессе их жизненного цикла. Эта задача становится ещё масштабнее в том случае, если речь идёт о разработке чего-то вроде фреймворка или даже обычной библиотеки, когда, например, нужно реагировать на то, что происходит со страницей и выполнять какие-то действия, зависящие от DOM.
Обзор
MutationObserver — это Web API, предоставляемое современными браузерами и предназначенное для обнаружения изменений в DOM. С помощью этого API можно наблюдать за добавлением или удалением узлов DOM, за изменением атрибутов элементов, или, например, за изменением текстов текстовых узлов. Зачем это нужно?
Есть немало ситуаций, в которых API MutationObserver
может оказаться очень кстати. Например:
- Вам нужно уведомить пользователя веб-приложения о том, что на странице, с которой он работает, произошли какие-то изменения.
- Вы работаете над новым интересным JS-фреймворком, который динамически загружает JavaScript-модули, основываясь на изменениях DOM.
- Возможно, вы работаете над WYSIWYG-редактором и пытаетесь реализовать функционал отмены и повтора действий. Воспользовавшись API
MutationObserver
, вы будете, в любой момент, знать о том, какие изменения произошли на странице, а это означает, что вы легко сможете их отменять.
Текстовый редактор, работающий в браузере
Выше приведены лишь несколько ситуаций, в которых возможности MutationObserver
могут оказаться полезными. На самом деле их гораздо больше.
Как пользоваться MutationObserver
Использовать MutationObserver
в веб-приложениях довольно просто. Нужно создать экземпляр MutationObserver
, передав соответствующему конструктору функцию, которая будет вызываться каждый раз, когда в DOM будут происходить изменения. Первый аргумент функции — это коллекция всех произошедших мутаций в виде единого пакета. Для каждой мутации предоставляется информация о её типе и об изменениях, которые она представляет.
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});
У созданного объекта есть три метода:
- Метод
observe
запускает процесс отслеживания изменений DOM. Он принимает два аргумента — узел DOM, за которым нужно наблюдать, и объект с параметрами. - Метод
disconnect
останавливает наблюдение за изменениями. - Метод
takeRecords
возвращает текущую очередь экземпляраMutationObserver
, после чего очищает её.
Вот как включить наблюдение за изменениями:
// Запускаем наблюдение за изменениями в корневом HTML-элементе страницы
mutationObserver.observe(document.documentElement, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
});
Теперь предположим, что в DOM имеется простейший элемент div
:
<div id="sample-div" class="test"> Simple div </div>
Используя jQuery, можно удалить атрибут class
из этого элемента:
$("#sample-div").removeAttr("class");
Благодаря тому, что мы начали наблюдение за изменениями, предварительно вызвав mutationObserver.observe(...)
, и тому, что функция, реагирующая на поступление нового пакета изменений, выводит полученные данные в консоль, мы увидим в консоли содержимое соответствующего объекта MutationRecord:
Объект MutationRecord
Тут можно видеть мутации, причиной которых стало удаление атрибута class
.
И, наконец, для того, чтобы прекратить наблюдение за DOM после того, как работа завершена, можно сделать следующее:
// Прекратим наблюдение за изменениями
mutationObserver.disconnect();
Поддержка MutationObserver в различных браузерах
API MutationObserver
пользуется широкой поддержкой в браузерах:
Поддержка MutationObserver
Альтернативы MutationObserver
Стоит отметить, что механизм наблюдениями за изменениями DOM, который предлагает MutationObserver
, не всегда был доступен разработчикам. Чем они пользовались до появления MutationObserver
? Вот несколько вариантов:
- Опрос (polling).
- Механизм
MutationEvents
. - CSS-анимация.
▍Опрос
Самый простой и незамысловатый способ отслеживания изменений DOM — опрос. Используя метод setInterval
можно запланировать периодическое выполнение функции, которая проверяет DOM на предмет изменений. Естественно, использование этого метода значительно снижает производительность веб-приложений.
▍MutationEvents
API MutationEvents было представлено в 2000 году. Несмотря на то, что это API позволяет решать возлагаемые на него задачи, события мутации вызываются после каждого изменения DOM, что, опять же, приводит к проблемам с производительностью. Теперь API MutationEvents
признано устаревшим и вскоре современные браузеры перестанут его поддерживать.
Поддержка MutationEvents
▍CSS-анимация
На самом деле, альтернатива MutationObserver
в виде CSS-анимаций может показаться несколько странной. Причём тут анимация? В целом, идея тут заключается в создании анимации, которая будет вызвана после того, как элемент будет добавлен в DOM. В момент запуска анимации будет вызвано событие animationstart
. Если назначить обработчик для этого события, можно узнать точное время добавления нового элемента в DOM. Время выполнения анимации при этом должно быть настолько маленьким, чтобы она была практически незаметна для пользователя.
Для того чтобы воспользоваться этим методом, сначала нужен родительский элемент, за добавлением в который новых узлов мы хотим наблюдать:
<div id="container-element"></div>
Для организации наблюдения за добавлением в него узлов нужно настроить последовательность ключевых кадров CSS-анимации, которые запустятся при добавлении узла:
@keyframes nodeInserted {
from { opacity: 0.99; }
to { opacity: 1; }
}
После создания ключевых кадров анимация должна быть применена к элементам, за которыми нужно наблюдать. Обратите внимание на длительность анимации. Она очень мала, благодаря чему анимация оказывается практически незаметной.
#container-element * {
animation-duration: 0.001s;
animation-name: nodeInserted;
}
Тут мы добавляем анимацию ко всем узлам-потомкам элемента container-element
. Когда анимация заканчивается, вызывается соответствующее событие.
Теперь нужна JS-функция, которая будет играть роль обработчика событий. Внутри функции, в первую очередь, необходимо выполнить проверку event.animationName
для того, чтобы убедиться, что это — именно та анимация, которая нас интересует.
var insertionListener = function(event) {
// Убедимся в том, что это именно та анимация, которая нас интересует
if (event.animationName === "nodeInserted") {
console.log("Node has been inserted: " + event.target);
}
}
Теперь добавим обработчик события к родительскому элементу. В разных браузерах это делается по-разному:
document.addEventListener("animationstart", insertionListener, false); // standard + firefox
document.addEventListener("MSAnimationStart", insertionListener, false); // IE
document.addEventListener("webkitAnimationStart", insertionListener, false); // Chrome + Safari
Вот как обстоит дело с поддержкой CSS-анимации в различных браузерах.
Поддержка CSS-анимации в различных браузерах
Итоги
Мы рассмотрели API MutationObserver
и альтернативные способы наблюдения за изменениями DOM. Надо отметить, что MutationObserver
имеет множество преимуществ перед этими альтернативами. В целом, можно говорить о том, что это API способно сообщать о любых изменениях, которые могут возникать в DOM, о том, что оно хорошо оптимизировано, давая информацию об изменениях, собранную в пакеты. Кроме того, API MutationObserver
пользуется поддержкой всех основных современных браузеров, существуют и полифиллы для него, основанные на MutationEvents
.
Автор материала отмечает, что MutationObserver
занимает центральное место в библиотеке SessionStack, которая направлена на организацию сбора данных о том, что происходит с веб-страницами.
Предыдущие части цикла статей:
Часть 1: Как работает JS: обзор движка, механизмов времени выполнения, стека вызовов
Часть 2: Как работает JS: о внутреннем устройстве V8 и оптимизации кода
Часть 3: Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними
Часть 4: Как работает JS: цикл событий, асинхронность и пять способов улучшения кода с помощью async / await
Часть 5: Как работает JS: WebSocket и HTTP/2+SSE. Что выбрать?
Часть 6: Как работает JS: особенности и сфера применения WebAssembly
Часть 7: Как работает JS: веб-воркеры и пять сценариев их использования
Часть 8: Как работает JS: сервис-воркеры
Часть 9: Как работает JS: веб push-уведомления
Уважаемые читатели! Пользуетесь ли вы MutationObserver в своих проектах?
Автор: ru_vds