Conociendo la API MutationObserver

 

 

 

  • Accesibilidad para diseñadores, con Stéphanie Walter
  • Clase magistral de CSS moderno avanzado, con Manuel Matuzović

  • Índice
    1. Sintaxis básica para un observador de mutaciones
    2. Opciones para configurar un MutationObserver
    3. Observación de cambios en elementos secundarios mediante childList
    4. Observación de cambios en los atributos de un elemento
    5. Observación de cambios en los datos de los caracteres
      1. Desafíos al observar cambios en los datos de los personajes
    6. Observación de cambios en atributos especificados
    7. Observación de cambios en los nodos y su subárbol
      1. childList Con subárbol
      2. Atributos con subárbol
      3. caracterData Con subárbol
    8. Grabar valores antiguos
      1. atributoOldValue
      2. carácterDatosAntiguoValor
    9. Interceptar mutaciones usando takeRecords()
    10. Observación de múltiples cambios utilizando un solo observador
    11. Mover un árbol de nodos que se está observando
    12. Conclusión

    A veces es necesario monitorear los cambios en el DOM en marcos y aplicaciones web complejos. Mediante explicaciones junto con demostraciones interactivas, este artículo le mostrará cómo puede utilizar la API MutationObserver para que la observación de los cambios DOM sea relativamente fácil.

     

    En aplicaciones web complejas, los cambios de DOM pueden ser frecuentes. Como resultado, hay casos en los que es posible que su aplicación deba responder a un cambio específico en el DOM.

    Durante algún tiempo, la forma aceptada de buscar cambios en el DOM era mediante una función llamada Mutation Events , que ahora está en desuso . El reemplazo aprobado por el W3C para Mutation Events es la API MutationObserver , que es lo que discutiré en detalle en este artículo.

    Varios artículos y referencias anteriores analizan por qué se reemplazó la característica anterior, por lo que no entraré en detalles sobre eso aquí (además del hecho de que no podría hacerle justicia). La MutationObserverAPI tiene compatibilidad casi completa con el navegador , por lo que podemos usarla de forma segura en la mayoría (si no en todos) los proyectos, en caso de que surja la necesidad.

    Sintaxis básica para un observador de mutaciones

    A MutationObserverse puede utilizar de varias maneras diferentes, que cubriré en detalle en el resto de este artículo, pero la sintaxis básica de a MutationObserverse ve así:

    let observer = new MutationObserver(callback); function callback (mutations) { // do something here}observer.observe(targetNode, observerOptions);

    La primera línea crea una nueva MutationObserverusando el MutationObserver()constructor. El argumento pasado al constructor es una función de devolución de llamada que se llamará en cada cambio de DOM que califique.

    La forma de determinar qué califica para un observador en particular es mediante la línea final del código anterior. En esa línea, estoy usando el observe()método del MutationObserverpara comenzar a observar. Puedes comparar esto con algo como addEventListener(). Tan pronto como adjunte un oyente, la página "escuchará" el evento especificado. De manera similar, cuando comience a observar, la página comenzará a "observar" el objeto especificado MutationObserver.

    El observe()método toma dos argumentos: El objetivo , que debe ser el nodo o árbol de nodos en el que observar los cambios; y un objeto de opciones , que es un MutationObserverInitobjeto que le permite definir la configuración para el observador.

    La última característica básica clave de a MutationObserveres el disconnect()método. Esto le permite dejar de observar los cambios especificados y se ve así:

     

    observer.disconnect();

    Opciones para configurar un MutationObserver

    Como se mencionó, el observe()método de a MutationObserverrequiere un segundo argumento que especifique las opciones para describir el MutationObserver. Así es como se vería el objeto de opciones con todos los pares posibles de propiedad/valor incluidos:

    let options = { childList: true, attributes: true, characterData: false, subtree: false, attributeFilter: ['one', 'two'], attributeOldValue: false, characterDataOldValue: false};

    Al configurar las MutationObserveropciones, no es necesario incluir todas estas líneas. Los incluyo simplemente como referencia, para que pueda ver qué opciones están disponibles y qué tipos de valores pueden tomar. Como puede ver, todos excepto uno son booleanos.

    Para que MutationObservera funcione, al menos uno de childList, attributeso characterDatadebe establecerse en true; de lo contrario, se generará un error. Las otras cuatro propiedades funcionan en conjunto con una de esas tres (más sobre esto más adelante).

    Hasta ahora simplemente he pasado por alto la sintaxis para darle una visión general. La mejor manera de considerar cómo funciona cada una de estas características es proporcionando ejemplos de código y demostraciones en vivo que incorporen las diferentes opciones. Eso es lo que haré durante el resto de este artículo.

    Observación de cambios en elementos secundarios mediante childList

    El primero y más simple MutationObserverque puede iniciar es uno que busca nodos secundarios de un nodo específico (generalmente un elemento) para agregarlos o eliminarlos. Para mi ejemplo, voy a crear una lista desordenada en mi HTML y quiero saber cuándo se agrega o elimina un nodo secundario de este elemento de la lista.

    El HTML de la lista se ve así:

    ul liApples/li liOranges/li liBananas/li liPeaches/li/ul

    El JavaScript para mi MutationObserverincluye lo siguiente:

    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);

    Esto es sólo una parte del código. Para abreviar, muestro las secciones más importantes que tratan de la MutationObserverAPI en sí.

    Observe cómo estoy recorriendo el mutationsargumento, que es un MutationRecordobjeto que tiene varias propiedades diferentes. En este caso, leo la typepropiedad y registro un mensaje que indica que el navegador ha detectado una mutación que califica. Además, observe cómo paso el mListelemento (una referencia a mi lista HTML) como elemento de destino (es decir, el elemento en el que quiero observar los cambios).

    • Ver demostración interactiva completa →

    Utilice los botones para iniciar y detener el MutationObserver. Los mensajes de registro ayudan a aclarar lo que está sucediendo. Los comentarios en el código también proporcionan alguna explicación.

     

    Tenga en cuenta algunos puntos importantes aquí:

    • La función de devolución de llamada (que he llamado mCallback, para ilustrar que puedes nombrarla como quieras) se activará cada vez que se detecte una mutación exitosa y después de que observe()se ejecute el método.
    • En mi ejemplo, el único 'tipo' de mutación que califica es childList, por lo que tiene sentido buscar este al recorrer MutationRecord. Buscar cualquier otro tipo en este caso no serviría de nada (los otros tipos se utilizarán en demostraciones posteriores).
    • Usando childList, puedo agregar o eliminar un nodo de texto del elemento objetivo y esto también calificaría. Por lo tanto, no tiene por qué ser un elemento que se agrega o elimina.
    • En este ejemplo, solo calificarán los nodos secundarios inmediatos. Más adelante en el artículo, le mostraré cómo esto se puede aplicar a todos los nodos secundarios, nietos, etc.

    Observación de cambios en los atributos de un elemento

    Otro tipo común de mutación que quizás desee realizar un seguimiento es cuando cambia un atributo de un elemento específico. En la próxima demostración interactiva, observaré cambios en los atributos de un elemento de párrafo.

    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);
    • Pruebe la demostración →

    Nuevamente, he abreviado el código para mayor claridad, pero las partes importantes son:

    • El optionsobjeto está usando la attributespropiedad configurada en truepara indicarle MutationObserverque quiero buscar cambios en los atributos del elemento objetivo.
    • El tipo de mutación que estoy probando en mi bucle es attributes, el único que califica en este caso.
    • También estoy usando la attributeNamepropiedad del mutationobjeto, que me permite saber qué atributo se cambió.
    • Cuando activo el observador, paso el elemento de párrafo por referencia, junto con las opciones.

    En este ejemplo, se utiliza un botón para alternar el nombre de una clase en el elemento HTML de destino. La función de devolución de llamada en el observador de mutaciones se activa cada vez que se agrega o elimina una clase.

    Observación de cambios en los datos de los caracteres

    Otro cambio que quizás quieras buscar en tu aplicación son las mutaciones en los datos de los personajes; es decir, cambios a un nodo de texto específico. Esto se hace estableciendo la characterDatapropiedad trueen el optionsobjeto. Aquí está el código:

    let options = { characterData: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation.type === 'characterData') { // Do something here... } }}

    Observe nuevamente que lo typeque se busca en la función de devolución de llamada es characterData.

     

    • Ver demostración en vivo →

    En este ejemplo, busco cambios en un nodo de texto específico, al que me dirijo mediante element.childNodes[0]. Esto es un poco complicado pero servirá para este ejemplo. El texto es editable por el usuario mediante el contenteditableatributo de un elemento de párrafo.

    Desafíos al observar cambios en los datos de los personajes

    Si ha jugado con contenteditable, es posible que sepa que existen atajos de teclado que permiten la edición de texto enriquecido. Por ejemplo, CTRL-B pone el texto en negrita, CTRL-I pone el texto en cursiva, etc. Esto dividirá el nodo de texto en varios nodos de texto, por lo que notará que MutationObserverdejará de responder a menos que edite el texto que todavía se considera parte del nodo original. Cómo Localizar un Celular Móvil [ Contenido Actualizado Abril del 2024 ]

    También debo señalar que si elimina todo el texto, ya MutationObserverno se activará la devolución de llamada. Supongo que esto sucede porque una vez que el nodo de texto desaparece, el elemento de destino ya no existe. Para combatir esto, mi demostración deja de observar cuando se elimina el texto, aunque las cosas se vuelven un poco complicadas cuando usas atajos de texto enriquecido.

    Pero no se preocupe, más adelante en este artículo analizaré una mejor manera de utilizar la characterDataopción sin tener que lidiar con tantas de estas peculiaridades.

    Observación de cambios en atributos especificados

    Anteriormente les mostré cómo observar cambios en los atributos de un elemento específico. En ese caso, aunque la demostración activa un cambio de nombre de clase, podría haber cambiado cualquier atributo en el elemento especificado. Pero ¿qué pasa si quiero observar cambios en uno o más atributos específicos mientras ignoro los demás?

    Puedo hacerlo usando la attributeFilterpropiedad opcional en el optionobjeto. He aquí un ejemplo:

    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... } }}

    Como se muestra arriba, la attributeFilterpropiedad acepta una serie de atributos específicos que deseo monitorear. En este ejemplo, MutationObserveractivará la devolución de llamada cada vez que se modifiquen uno o más de los atributos hidden, contenteditableo .data-par

    • Ver demostración en vivo →

    Nuevamente me estoy centrando en un elemento de párrafo específico. Observe el menú desplegable de selección que elige qué atributo se cambiará. El draggableatributo es el único que no califica ya que no lo especifiqué en mis opciones.

    Observe en el código que estoy usando nuevamente la attributeNamepropiedad del MutationRecordobjeto para registrar qué atributo se cambió. Y, por supuesto, al igual que con las otras demostraciones, MutationObserverno comenzará a monitorear los cambios hasta que se haga clic en el botón "iniciar".

     

    Otra cosa que debo señalar aquí es que no necesito establecer el attributesvalor trueen este caso; está implícito debido a que attributesFilterestá configurado como verdadero. Es por eso que mi objeto de opciones podría verse de la siguiente manera y funcionaría igual:

    let options = { attributeFilter: ['hidden', 'contenteditable', 'data-par']}

    Por otro lado, si configuro explícitamente attributesjunto falsecon una attributeFiltermatriz, no funcionaría porque el falsevalor tendría prioridad y la opción de filtro se ignoraría.

    Observación de cambios en los nodos y su subárbol

    Hasta ahora, al configurar cada MutationObserver, solo he estado tratando con el elemento objetivo en sí y, en el caso de childList, los hijos inmediatos del elemento. Pero ciertamente podría haber un caso en el que quisiera observar cambios en uno de los siguientes:

    • Un elemento y todos sus elementos secundarios;
    • Uno o más atributos de un elemento y de sus elementos secundarios;
    • Todos los nodos de texto dentro de un elemento.

    Todo lo anterior se puede lograr utilizando la subtreepropiedad del objeto de opciones.

    childList Con subárbol

    Primero, busquemos cambios en los nodos secundarios de un elemento, incluso si no son hijos inmediatos. Puedo modificar mi objeto de opciones para que se vea así:

    options = { childList: true, subtree: true}

    Todo lo demás en el código es más o menos igual que en el childListejemplo anterior, junto con algunas etiquetas y botones adicionales.

    • Ver demostración en vivo →

    Aquí hay dos listas, una anidada dentro de la otra. Cuando se MutationObserverinicia, la devolución de llamada activará los cambios en cualquiera de las listas. Pero si volviera a cambiar la subtreepropiedad a false(el valor predeterminado cuando no está presente), la devolución de llamada no se ejecutaría cuando se modifique la lista anidada.

    Atributos con subárbol

    Aquí hay otro ejemplo, esta vez usando subtreewith attributesy attributeFilter. Esto me permite observar cambios en los atributos no solo en el elemento de destino sino también en los atributos de cualquier elemento secundario del elemento de destino:

    options = { attributes: true, attributeFilter: ['hidden', 'contenteditable', 'data-par'], subtree: true}
    • Ver demostración en vivo →

    Esto es similar a la demostración de atributos anterior, pero esta vez configuré dos elementos de selección diferentes. El primero modifica los atributos del elemento del párrafo de destino, mientras que el otro modifica los atributos de un elemento secundario dentro del párrafo.

    Nuevamente, si volviera a configurar la subtreeopción false(o la eliminara), el segundo botón de alternancia no activaría la devolución de MutationObserverllamada. Y, por supuesto, podría omitirlo attributeFilterpor completo y buscaría MutationObservercambios en cualquier atributo en el subárbol en lugar de los especificados.

     

    caracterData Con subárbol

    Recuerde que en la characterDatademostración anterior, hubo algunos problemas con la desaparición del nodo objetivo y luego MutationObserverdejó de funcionar. Si bien hay formas de evitarlo, es más fácil apuntar a un elemento directamente en lugar de a un nodo de texto, luego usar la subtreepropiedad para especificar que quiero que todos los datos de caracteres dentro de ese elemento, sin importar cuán profundamente anidado esté, activen el MutationObserverllamar de vuelta.

    Mis opciones en este caso se verían así:

    options = { characterData: true, subtree: true}
    • Ver demostración en vivo →

    Después de iniciar el observador, intente usar CTRL-B y CTRL-I para formatear el texto editable. Notarás que esto funciona mucho más efectivamente que el characterDataejemplo anterior. En este caso, los nodos secundarios divididos no afectan al observador porque estamos observando todos los nodos dentro del nodo objetivo, en lugar de un solo nodo de texto.

    Grabar valores antiguos

    A menudo, cuando observe cambios en el DOM, querrá tomar nota de los valores antiguos y posiblemente almacenarlos o usarlos en otro lugar. Esto se puede hacer usando algunas propiedades diferentes en el optionsobjeto.

    atributoOldValue

    Primero, intentemos cerrar sesión en el valor del atributo anterior después de cambiarlo. Así es como se verán mis opciones junto con mi devolución de llamada:

    options = { attributes: true, attributeOldValue: true}function mCallback (mutations) { for (let mutation of mutations) { if (mutation.type === 'attributes') { // Do something here... } }}
    • Ver demostración en vivo →

    Observe el uso de las propiedades attributeNamey oldValuedel MutationRecordobjeto. Pruebe la demostración ingresando diferentes valores en el campo de texto. Observe cómo el registro se actualiza para reflejar el valor anterior que se almacenó.

    carácterDatosAntiguoValor

    De manera similar, así es como se verían mis opciones si quiero registrar datos de personajes antiguos:

    options = { characterData: true, subtree: true, characterDataOldValue: true}
    • Ver demostración en vivo →

    Observe que los mensajes de registro indican el valor anterior. Las cosas se ponen un poco complicadas cuando agregas HTML mediante comandos de texto enriquecido a la mezcla. No estoy seguro de cuál se supone que sea el comportamiento correcto en ese caso, pero es más sencillo si lo único dentro del elemento es un único nodo de texto.

    Interceptar mutaciones usando takeRecords()

    Otro método del MutationObserverobjeto que aún no he mencionado es takeRecords(). Este método le permite interceptar más o menos las mutaciones que se detectan antes de que sean procesadas por la función de devolución de llamada.

    Puedo usar esta función usando una línea como esta:

    let myRecords = observer.takeRecords();

    Esto almacena una lista de los cambios DOM en la variable especificada. En mi demostración , ejecuto este comando tan pronto como se hace clic en el botón que modifica el DOM. Tenga en cuenta que los botones de inicio y agregar/eliminar no registran nada. Esto se debe a que, como se mencionó, estoy interceptando los cambios del DOM antes de que la devolución de llamada los procese.

     

    Observe, sin embargo, lo que estoy haciendo en el detector de eventos que detiene al observador:

    btnStop.addEventListener('click', function () { observer.disconnect(); if (myRecords) { console.log(`${myRecords[0].target} was changed using the ${myRecords[0].type} option.`); }}, false);

    Como puede ver, después de detener el uso del observador observer.disconnect(), accedo al registro de mutación que fue interceptado y registro el elemento objetivo, así como el tipo de mutación que se registró. Si hubiera estado observando varios tipos de cambios, entonces el registro almacenado tendría más de un elemento, cada uno con su propio tipo.

    Cuando se intercepta un registro de mutación de esta manera llamando a takeRecords(), se vacía la cola de mutaciones que normalmente se enviaría a la función de devolución de llamada. Entonces, si por alguna razón necesita interceptar estos registros antes de que se procesen, takeRecords()sería útil.

    Observación de múltiples cambios utilizando un solo observador

    Tenga en cuenta que si busco mutaciones en dos nodos diferentes de la página, puedo hacerlo utilizando el mismo observador. Esto significa que después de llamar al constructor, puedo ejecutar el observe()método para tantos elementos como quiera.

    Así, después de esta línea:

    observer = new MutationObserver(mCallback);

    Luego puedo tener múltiples observe()llamadas con diferentes elementos como primer argumento:

    observer.observe(mList, options);observer.observe(mList2, options);
    • Ver demostración en vivo →

    Inicie el observador, luego pruebe los botones agregar/eliminar para ambas listas. El único inconveniente aquí es que si presiona uno de los botones de "detener", el observador dejará de observar ambas listas, no solo la que está apuntando.

    Mover un árbol de nodos que se está observando

    Una última cosa que señalaré es que a MutationObservercontinuará observando cambios en un nodo específico incluso después de que ese nodo haya sido eliminado de su elemento principal.

    Por ejemplo, pruebe la siguiente demostración:

    • Ver demostración en vivo →

    Este es otro ejemplo que se utiliza childListpara monitorear cambios en los elementos secundarios de un elemento de destino. Observe el botón que desconecta la sublista, que es la que se está observando. Haga clic en el botón "Inicio...", luego haga clic en el botón "Mover..." para mover la lista anidada. Incluso después de que la lista se elimina de su padre, MutationObservercontinúa observando los cambios especificados. No es una gran sorpresa que esto suceda, pero es algo a tener en cuenta.

    Conclusión

    Esto cubre casi todas las características principales de la MutationObserverAPI. Espero que este análisis profundo te haya resultado útil para familiarizarte con este estándar. Como se mencionó, la compatibilidad del navegador es sólida y puede leer más sobre esta API en las páginas de MDN .

    He puesto todas las demostraciones de este artículo en una colección de CodePen , en caso de que quieras tener un lugar fácil para jugar con las demostraciones.

    (dm, il)Explora más en

    • API
    • javascript
    • Aplicaciones
    • Marcos





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Conociendo la API MutationObserver

    Conociendo la API MutationObserver

    Accesibilidad para diseñadores, con Stéphanie Walter Clase magistral de CSS moderno avanzado, con Manuel Matuzović Índice

    programar

    es

    https://pseint.es/static/images/programar-conociendo-la-api-mutationobserver-980-0.jpg

    2024-04-04

     

    Conociendo la API MutationObserver
    Conociendo la API MutationObserver

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20