Detectar cambios en el DOM
-
13-09-2020 - |
Pregunta
Quiero ejecutar una función cuando se agrega algún div o entrada al html.es posible?
Por ejemplo, si se agrega una entrada de texto, entonces se debe llamar a la función.
Solución
actualización de 2015, nuevo MutationObserver
es compatible con los navegadores modernos:
Chrome 18+, Firefox 14+, IE 11+, Safari 6+
Si necesita apoyar a los mayores, puede intentar recurrir a otros enfoques como los mencionados en este 5 (!) Respuesta de un año a continuación.Habrá dragones.Disfrutar :)
¿Alguien más está cambiando el documento?Porque si tienes control total sobre los cambios solo necesitas crear los tuyos propios. domChanged
API, con una función o evento personalizado, y activarla/llamarla en cualquier lugar donde modifiques cosas.
El DOM Nivel-2 tiene Tipos de eventos de mutación, pero las versiones anteriores de IE no lo admiten.Tenga en cuenta que los eventos de mutación son obsoleto en la especificación de eventos DOM3 y tener un penalización por desempeño.
Puedes intentar emular el evento de mutación con onpropertychange
en es decir (y recurrir al enfoque de fuerza bruta si ninguno de ellos está disponible).
Para lleno domCambiar un intervalo podría ser excesivo.Imagine que necesita almacenar el estado actual de todo el documento y examinar que todas las propiedades de cada elemento sean las mismas.
Tal vez si solo estás interesado en los elementos y su orden (como mencionaste en tu pregunta), un getElementsByTagName("*")
puede trabajar.Esto se activará automáticamente si agrega un elemento, elimina un elemento, reemplaza elementos o cambia la estructura del documento.
Escribí una prueba de concepto:
(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);
Uso:
onDomChange(function(){
alert("The Times They Are a-Changin'");
});
Esto funciona en IE 5.5+, FF 2+, Chrome, Safari 3+ y Opera 9.6+
Otros consejos
Este es el enfoque final hasta ahora, con el código más pequeño:
ie9 +, ff, webkit:
usando mutationobserver y cayendo a la enfermedad eventos de mutación Si es necesario: (ejemplo a continuación, si solo para los cambios DOM relativos a los nodos adjuntos o eliminados)
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>
Recientemente escribí un complemento que hace exactamente eso: jquery.inicializar
Lo usas de la misma manera que .each
función
$(".some-element").initialize( function(){
$(this).css("color", "blue");
});
La diferencia de .each
es - toma su selector, en este caso .some-element
y espere nuevos elementos con este selector en el futuro; si se agrega dicho elemento, también se inicializará.
En nuestro caso, la función de inicialización simplemente cambia el color del elemento a azul.Entonces, si agregamos un nuevo elemento (no importa si es con ajax o incluso con el inspector F12 o algo así) como:
$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!
El complemento lo iniciará instantáneamente.Además, el complemento garantiza que un elemento se inicialice solo una vez.Entonces, si agregas un elemento, entonces .detach()
del cuerpo y luego agréguelo nuevamente, no se inicializará nuevamente.
$("<div/>").addClass('some-element').appendTo("body").detach()
.appendTo(".some-container");
//initialized only once
El complemento se basa en MutationObserver
- Funcionará en IE9 y 10 con dependencias como se detalla en el página Léame.
o simplemente puede crear crear su propio evento , que se ejecuta en todas partes
$("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");
});
Ejemplo completo http://jsfiddle.net/hbmaam/mq7nx/
El siguiente ejemplo fue adaptado de Mozilla Hacks ' blog poste y está usando Mutationobserver .
// 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();
Soporte del navegador: Chrome 18+, Firefox 14+, es decir, 11+, Safari 6 +
Usa el Interfaz MutationObserver como se muestra en Gabriele Romanato's
Chrome 18+, Firefox 14+, es decir, 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();
¿Qué tal, extendiendo un jQuery para esto?
(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+ ha construido soporte para esto (he escuchado no probado).