Обнаружить изменения в DOM
Я хочу, чтобы выполнить функцию, когда некоторые HTML или ввод добавляются в HTML. Это возможно?
Например, добавлен текстовый ввод, затем должна быть вызвана функция.
11 ответов
Обновление 2015 года, новый MutationObserver
поддерживается современными браузерами:
Chrome 18+, Firefox 14+, IE 11+, Safari 6+
Если вам нужно поддержать более старые, вы можете попытаться использовать другие подходы, подобные тем, которые упомянуты в этом5(!) -Летнем ответе ниже. Там будут драконы. Наслаждаться:)
Кто-то еще меняет документ? Потому что, если у вас есть полный контроль над изменениями, вам просто нужно создать свой собственныйdomChanged
API - с функцией или пользовательским событием - и вызывайте / вызывайте его везде, где вы что-то изменяете.
DOM Level-2 имеет типы событий Mutation, но старые версии IE его не поддерживают. Обратите внимание, что события мутации не рекомендуются в спецификации событий DOM3 и имеют снижение производительности.
Вы можете попытаться эмулировать событие мутации с onpropertychange
в IE (и вернуться к подходу грубой силы, если ни один из них не доступен).
Для полного domChange интервал может быть чрезмерным. Представьте, что вам нужно сохранить текущее состояние всего документа и проверить, чтобы все свойства каждого элемента были одинаковыми.
Может быть, если вас интересуют только элементы и их порядок (как вы упомянули в своем вопросе), getElementsByTagName("*")
может работать. Это срабатывает автоматически, если вы добавляете элемент, удаляете элемент, заменяете элементы или изменяете структуру документа.
Я написал подтверждение концепции:
(function (window) {
var last = +new Date();
var delay = 100; // default delay
// Manage event queue
var stack = [];
function callback() {
var now = +new Date();
if (now - last > delay) {
for (var i = 0; i < stack.length; i++) {
stack[i]();
}
last = now;
}
}
// Public interface
var onDomChange = function (fn, newdelay) {
if (newdelay) delay = newdelay;
stack.push(fn);
};
// Naive approach for compatibility
function naive() {
var last = document.getElementsByTagName('*');
var lastlen = last.length;
var timer = setTimeout(function check() {
// get current state of the document
var current = document.getElementsByTagName('*');
var len = current.length;
// if the length is different
// it's fairly obvious
if (len != lastlen) {
// just make sure the loop finishes early
last = [];
}
// go check every element in order
for (var i = 0; i < len; i++) {
if (current[i] !== last[i]) {
callback();
last = current;
lastlen = len;
break;
}
}
// over, and over, and over again
setTimeout(check, delay);
}, delay);
}
//
// Check for mutation events support
//
var support = {};
var el = document.documentElement;
var remain = 3;
// callback for the tests
function decide() {
if (support.DOMNodeInserted) {
window.addEventListener("DOMContentLoaded", function () {
if (support.DOMSubtreeModified) { // for FF 3+, Chrome
el.addEventListener('DOMSubtreeModified', callback, false);
} else { // for FF 2, Safari, Opera 9.6+
el.addEventListener('DOMNodeInserted', callback, false);
el.addEventListener('DOMNodeRemoved', callback, false);
}
}, false);
} else if (document.onpropertychange) { // for IE 5.5+
document.onpropertychange = callback;
} else { // fallback
naive();
}
}
// checks a particular event
function test(event) {
el.addEventListener(event, function fn() {
support[event] = true;
el.removeEventListener(event, fn, false);
if (--remain === 0) decide();
}, false);
}
// attach test events
if (window.addEventListener) {
test('DOMSubtreeModified');
test('DOMNodeInserted');
test('DOMNodeRemoved');
} else {
decide();
}
// do the dummy test
var dummy = document.createElement("div");
el.appendChild(dummy);
el.removeChild(dummy);
// expose
window.onDomChange = onDomChange;
})(window);
Использование:
onDomChange(function(){
alert("The Times They Are a-Changin'");
});
Это работает на IE 5.5+, FF 2+, Chrome, Safari 3+ и Opera 9.6+
На данный момент это наилучший подход с наименьшим кодом:
IE9 +, FF, Webkit:
Использование MutationObserver и возврат к устаревшим событиям Mutation, если необходимо:
(Пример ниже, если только для изменений DOM, касающихся узлов, добавленных или удаленных)
var observeDOM = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ){
if( !obj || !obj.nodeType === 1 ) return; // validation
if( MutationObserver ){
// define a new observer
var obs = new MutationObserver(function(mutations, observer){
callback(mutations);
})
// have the observer observe foo for changes in children
obs.observe( obj, { childList:true, subtree:true });
}
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
//------------< DEMO BELOW >----------------
// add item
var itemHTML = "<li><button>list item (click to delete)</button></li>",
listElm = document.querySelector('ol');
document.querySelector('body > button').onclick = function(e){
listElm.insertAdjacentHTML("beforeend", itemHTML);
}
// delete item
listElm.onclick = function(e){
if( e.target.nodeName == "BUTTON" )
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
// Observe a specific DOM element:
observeDOM( listElm, function(m){
var addedNodes = [], removedNodes = [];
m.forEach(record => record.addedNodes.length & addedNodes.push(...record.addedNodes))
m.forEach(record => record.removedNodes.length & removedNodes.push(...record.removedNodes))
console.clear();
console.log('Added:', addedNodes, 'Removed:', removedNodes);
});
// Insert 3 DOM nodes at once after 3 seconds
setTimeout(function(){
listElm.removeChild(listElm.lastElementChild);
listElm.insertAdjacentHTML("beforeend", Array(4).join(itemHTML));
}, 3000);
<button>Add Item</button>
<ol>
<li><button>list item (click to delete)</button></li>
<li><button>list item (click to delete)</button></li>
<li><button>list item (click to delete)</button></li>
<li><button>list item (click to delete)</button></li>
<li><em>…More will be added after 3 seconds…</em></li>
</ol>
Это пример использования MutationObserver из Mozilla, адаптированный из этого поста в блоге.
Chrome 18+, Firefox 14+, IE 11+, Safari 6+
// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');
// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };
// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
for(var mutation of mutationsList) {
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
var observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();
Я недавно написал плагин, который делает именно это - jquery.initialize
Вы используете его так же, как .each
функция
$(".some-element").initialize( function(){
$(this).css("color", "blue");
});
Отличие от .each
это - это берет ваш селектор, в этом случае .some-element
и ждите новых элементов с этим селектором в будущем, если такой элемент будет добавлен, он также будет инициализирован.
В нашем случае функция инициализации просто меняет цвет элемента на синий. Так что, если мы добавим новый элемент (неважно, с ajax или даже инспектором F12 или чем-то еще), например
$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!
Плагин начнет его немедленно. Также плагин гарантирует, что один элемент инициализируется только один раз. Так что, если вы добавите элемент, то .detach()
это из тела и затем добавьте это снова, это не будет инициализировано снова.
$("<div/>").addClass('some-element').appendTo("body").detach()
.appendTo(".some-container");
//initialized only once
Плагин основан на MutationObserver
- он будет работать на IE9 и 10 с зависимостями, как описано на странице readme.
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
console.log(mutations, observer);
// ...
});
// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});
Полные объяснения: /questions/12906600/est-slushatel-izmeneniya-dom-javascriptjquery/12906611#12906611
Или вы можете просто создать свое собственное событие, которое будет проходить везде
$("body").on("domChanged", function () {
//dom is changed
});
$(".button").click(function () {
//do some change
$("button").append("<span>i am the new change</span>");
//fire event
$("body").trigger("domChanged");
});
Полный пример http://jsfiddle.net/hbmaam/Mq7NX/
Используйте JQuery MutationObserver, как показано в блоге Габриэля Романато
Chrome 18+, Firefox 14+, IE 11+, Safari 6+
// The node to be monitored
var target = $( "#content" )[0];
// Create an observer instance
var observer = new MutationObserver(function( mutations ) {
mutations.forEach(function( mutation ) {
var newNodes = mutation.addedNodes; // DOM NodeList
if( newNodes !== null ) { // If there are new nodes added
var $nodes = $( newNodes ); // jQuery set
$nodes.each(function() {
var $node = $( this );
if( $node.hasClass( "message" ) ) {
// do something
}
});
}
});
});
// Configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true
};
// Pass in the target node, as well as the observer options
observer.observe(target, config);
// Later, you can stop observing
observer.disconnect();
8 лет спустя вот мое решение
Следующая функция выполняет указанный файл обратного вызова при изменении DOM.
watchDOMChange($('#dom-changes-here'),
function() { console.log(`DOM Change happened ${new Date().getTime()}\n`);},
{expires: 5000}
);
Функции
- Отменяйте последовательные изменения DOM, чтобы предотвратить слишком много выполнений
- Прекратить смотреть по истечении заданного времени
- Полезно наблюдать за изменением DOM из фреймворка, например Angular
function debounce(func, wait = 500 ) {
var timeout;
return function (...args) {
var context = this;
var later = function () {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Utility function watchDOMChange
*/
function watchDOMChange(el, callback, options={}) {
options = Object.assign({debounce:100, expires:1000}, options);
callback = callback || function() {};
// watch DOM change and fire 'dom-change' event
const observer = new MutationObserver(list => {
el.dispatchEvent(new CustomEvent('dom-change', {detail: list}));
});
observer.observe(el, {attributes: false, childList: true, subtree: true });
// listen to 'dom-change' event execute the callback function debounced
const debouncedFunc = debounce(callback, options.debounce);
el.addEventListener('dom-change', debouncedFunc);
// fire min. once
el.dispatchEvent(new CustomEvent('dom-change', {detail: undefined}));
// stop watching DOM change after expiry time
if (options.expires) {
setTimeout(function() {
observer.disconnect(el);
el.removeEventListener('dom-change', debouncedFunc);
}, options.expires);
}
}
Как насчет расширения JQuery для этого?
(function () {
var ev = new $.Event('remove'),
orig = $.fn.remove;
var evap = new $.Event('append'),
origap = $.fn.append;
$.fn.remove = function () {
$(this).trigger(ev);
return orig.apply(this, arguments);
}
$.fn.append = function () {
$(this).trigger(evap);
return origap.apply(this, arguments);
}
})();
$(document).on('append', function (e) { /*write your logic here*/ });
$(document).on('remove', function (e) { /*write your logic here*/ ) });
Jquery 1.9+ имеет встроенную поддержку для этого (я слышал, не проверял).
Нашел вопрос, поэтому обновил решение в 2022 году.
Мы видели различные решения, которые в основном включаютMutationObserver
.
Если кто-то хочет записать изменения DOM и сохранить их для воспроизведения через некоторое время, он может использовать
Редактировать:
Добавляя пример, вот подсказки:
можно использовать через или npm
Возьмем пример CDNCDN для записи событий изменения DOM:
Шаг 1: просто включите следующий тег script в<HTML><head>
ярлык
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.2/dist/rrweb-all.js" crossorigin="anonymous"></script>
Шаг 2: и добавьте приведенный ниже код в свой код для захвата событий, сгенерированных rrweb.rrwebrrweb .
<script>
var events = [];
rrweb.record({
emit(event) {
events.push(event);
// you can store this event anywhere and you can replay them later. ex: some JSON file, or DB
}
});
</script>
Этот пример в основном предназначен для записи событий для любого веб-приложения.
Чтобы знать и понимать в деталях (как записывать/воспроизводить), пожалуйста, прочитайте документацию rrweb .
Пример реплеера:
Это было сделано для отладки, однако добавлено сюда, чтобы каждый мог также проверить воспроизведенную сторону:
Используйте TrackChanges для обнаружения изменений HTML. Ссылка: https://www.npmjs.com/package/track-changes-js
Пример
let button = document.querySelector('.button');
trackChanges.addObserver('buttonObserver', () => button);
trackChanges.addHandler('buttonObserver', buttonHandler);
function buttonHandler(button) {
console.log(`Button created: ${button}`);
}