javascript ab-testing mutation-observers mutation-events optimizely

javascript - Cómo cambiar el contenido HTML mientras se carga en la página



ab-testing mutation-observers (2)

Hago pruebas A / B en nuestro sitio y hago la mayor parte de mi trabajo en un archivo JS que se carga en la parte superior de la página antes de que se muestre cualquier otra cosa, pero después de que se haya cargado jQuery, lo cual es útil a veces.

Tomando un ejemplo muy simple de cambiar una etiqueta H1, normalmente inyectaría un estilo en la cabeza para establecer la opacidad H1 en 0 y luego en DOMContentLoaded, manipularía el contenido H1 y luego establecería la opacidad en 1. La razón de esto es evitar un destello del contenido anterior antes de que se produzca el cambio; ocultar todo el objeto es más agradable a la vista.

Empecé a mirar la API MutationObserver. Lo he usado antes al cambiar el contenido en un cuadro de diálogo de superposición que el usuario podría abrir, lo que parece ser un enfoque bastante bueno y me pregunto si alguien ha logrado usar un MutationObserver para escuchar el documento cuando se está cargando por primera vez / analizar y realizar cambios en el documento antes del primer procesamiento y antes de DOMContentLoaded?

Este enfoque me permitiría cambiar el contenido de H1 sin tener que ocultarlo, cambiarlo y luego mostrarlo.

He intentado pero he fallado hasta ahora y acabo de leer sobre los eventos de mutación que deben ser obsoletos y me pregunto si estoy tratando de hacer algo que simplemente no es posible. Sin embargo, (no yo) hemos logrado poner un robot en Marte, así que espero poder resolver esto.

Entonces, ¿es posible usar MutationObservers para cambiar el contenido HTML sobre la marcha mientras se carga / analiza la página?

Gracias por cualquier ayuda o cualquier puntero.

Saludos, Nick


Hago pruebas A / B para ganarme la vida y uso MutationObservers con bastante frecuencia con buenos resultados, pero mucho más a menudo solo hago encuestas largas, que en realidad es lo que la mayoría de las plataformas de terceros hacen bajo el capó cuando usas su WYSIWYG (o a veces incluso sus editores de código). Un ciclo de 50 milisegundos no debería ralentizar la página o causar FOUC.

Generalmente uso un patrón simple como:

var poller = setInterval(function(){ if(document.querySelector(''#question-header'') !== null) { clearInterval(poller); //Do something } }, 50);

Puede obtener cualquier elemento DOM utilizando un selector de chisporroteo como lo haría en jQuery con document.querySelector, que a veces es lo único para lo que necesita una biblioteca.

De hecho, hacemos esto tan a menudo en mi trabajo que tenemos un proceso de compilación y una biblioteca de módulos que incluye una función llamada Cuándo, que hace exactamente lo que está buscando. Esa función particular verifica jQuery y el elemento, pero sería trivial modificar la biblioteca para no confiar en jQuery (confiamos en jQuery ya que está en la mayoría de los sitios de nuestros clientes y lo usamos para muchas cosas).

Hablando de plataformas de prueba de terceros y bibliotecas javascript, dependiendo de la implementación, muchas de las plataformas disponibles (como Optimizely, Qubit y Creo Monetate) incluyen una versión de jQuery (algunas veces recortada) que está disponible inmediatamente al ejecutar su código , así que es algo a tener en cuenta si está utilizando una plataforma de terceros.


Los documentos en MDN tienen un ejemplo incompleto genérico y no muestran las trampas comunes. La biblioteca de resumen de mutaciones proporciona un contenedor amigable para los humanos, pero como todos los contenedores, agrega gastos generales. Consulte Rendimiento de MutationObserver para detectar nodos en DOM completo .

Crea e inicia el observador.

Usemos un MutationObserver recursivo en todo el documento que informa todos los nodos agregados / eliminados.

var observer = new MutationObserver(onMutation); observer.observe(document, { childList: true, // report added/removed nodes subtree: true, // observe any descendant elements });

Enumeración ingenua de nodos añadidos.

Ralentiza la carga de páginas enormemente grandes / complejas, consulte Rendimiento .
A veces se pierden los elementos H1 fusionados en el contenedor principal, consulte la siguiente sección.

function onMutation(mutations) { mutations.forEach(mutation, mutation => Array.prototype .filter.call(mutation.addedNodes, added => added.localName == ''h1'' && added.textContent.match(/foo/) ).forEach(h1 => h1.innerHTML = h1.innerHTML.replace(/foo/, ''bar'') ); ); }

Enumeración eficiente de nodos agregados.

Ahora la parte difícil. Los nodos en un registro de mutación pueden ser contenedores mientras se carga una página (como el bloque de encabezado del sitio completo con todos sus elementos informados como un solo nodo agregado): la especificación no requiere que cada nodo agregado se enumere individualmente, por lo que '' querySelectorAll que mirar dentro de cada elemento usando querySelectorAll (extremadamente lento) o getElementsByTagName (extremadamente rápido).

function onMutation(mutations) { for (var i = 0, len = mutations.length; i < len; i++) { var added = mutations[i].addedNodes; for (var j = 0, lenAdded = added.length; j < lenAdded; j++) { var node = added[j]; var found; if (node.localName === ''h1'') { found = [node]; } else if (node.children && node.children.length) { found = node.getElementsByTagName(''h1''); } else { continue; } for (var k = 0, lenFound = found.length; k < lenFound; k++) { var h1 = found[k]; if (!h1.parentNode || !h1.textContent.match(/foo/)) { // either removed in another mutation or has no text to replace continue; } var walker = document.createTreeWalker(h1, NodeFilter.SHOW_TEXT); while (walker.nextNode()) { var textNode = walker.currentNode; var text = textNode.nodeValue; if (text.match(/foo/)) { textNode.nodeValue = text.replace(/foo/, ''bar''); } } } } } }

¿Por qué los dos feos vainilla for bucles? Debido a que forEach and filter y ES2015 for (val of array) podrían ser muy lentos en algunos navegadores, consulte Rendimiento de MutationObserver para detectar nodos en DOM completo .

¿Por qué el TreeWalker ? Para preservar los oyentes de eventos adjuntos a subelementos. Para cambiar solo los nodos de Text : no tienen nodos secundarios, y cambiarlos no desencadena una nueva mutación porque hemos usado childList: true , no characterData: true .

Procesando elementos relativamente raros a través de la colección HTMLC en vivo sin enumerar mutaciones.

Por lo tanto, buscamos un elemento que se supone que se usa raramente como etiqueta H1, IFRAME, etc. En este caso, podemos simplificar y acelerar la devolución de llamada del observador con una colección HTMLC actualizada automáticamente que devuelve getElementsByTagName.

var h1s = document.getElementsByTagName(''h1''); function onMutation(mutations) { if (mutations.length == 1) { // optimize the most frequent scenario: one element is added/removed var added = mutations[0].addedNodes[0]; if (!added || (added.localName !== ''h1'' && !added.children.length)) { // so nothing was added or non-H1 with no child elements return; } } // H1 is supposed to be used rarely so there''ll be just a few elements for (var i = 0, len = h1s.length; i < len; i++) { var h1 = h1s[i]; if (!h1.textContent.match(/foo/)) { continue; } var walker = document.createTreeWalker(h1, NodeFilter.SHOW_TEXT); while (walker.nextNode()) { var textNode = walker.currentNode; var text = textNode.nodeValue; if (text.match(/foo/)) { textNode.nodeValue = text.replace(/foo/, ''bar''); } } } }