¿Cómo comprobar si un elemento se ha cargado en una página antes de ejecutar un script?

7 minutos de lectura

avatar de usuario de user2052567
usuario2052567

Así que estoy creando mi página en la plantilla de mi empresa, que solo nos permite acceder al cuerpo de la página. No tenemos acceso a la etiqueta de encabezado y hay scripts cargados en una parte inferior de la página a la que no tenemos acceso. Uno de estos scripts carga dinámicamente un elemento en la página. Necesito ejecutar otra secuencia de comandos en ese elemento, pero debido a que el elemento no se carga en la página hasta que mi secuencia de comandos ya se ha ejecutado, no puedo acceder a ese elemento. ¿Hay alguna manera de verificar si ese elemento ya se ha cargado en la página antes de ejecutar mi secuencia de comandos?

Avísame si necesito explicarme mejor.

<head>Don't have access</head>


<body>
   <!--Needs to manipulate the element created by the companyScript.js--> 
   <script src="https://stackoverflow.com/questions/34863788/myScript.js"></script>

   <!--Script that runs after mine, which I don't have access too-->
   <script src="companyScript.js">
       /*Dynamically adds <div class="element"></div> to page*/
   </script>
</body>

  • escriba un script para agregar su script en la parte inferior de la página para que se cargue después

    – Juan 5

    18/01/2016 a las 20:49


  • If ( $('.element').length )

    – adeneo

    18/01/2016 a las 20:53

  • Use el código que agrega el elemento para ejecutar su código que afectaría a ese elemento. Necesita una mejor comprensión del problema para obtener respuestas de mayor calidad y menos adivinanzas

    – charlietfl

    18/01/2016 a las 20:56


  • @ johnny5 que no se ocupa de si existe o no un elemento agregado dinámicamente

    – charlietfl

    18/01/2016 a las 21:00


Luché contra un oso una vez. Avatar de usuario
Una vez luché con un oso.

Versión 2022, con MutationObserver

Reescribió el código 2020 para usar un MutationObserver en lugar de votar.

/**
 * Wait for an element before resolving a promise
 * @param {String} querySelector - Selector of element to wait for
 * @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout              
 */
function waitForElement(querySelector, timeout){
  return new Promise((resolve, reject)=>{
    var timer = false;
    if(document.querySelectorAll(querySelector).length) return resolve();
    const observer = new MutationObserver(()=>{
      if(document.querySelectorAll(querySelector).length){
        observer.disconnect();
        if(timer !== false) clearTimeout(timer);
        return resolve();
      }
    });
    observer.observe(document.body, {
      childList: true, 
      subtree: true
    });
    if(timeout) timer = setTimeout(()=>{
      observer.disconnect();
      reject();
    }, timeout);
  });
}

waitForElement("#idOfElementToWaitFor", 3000).then(function(){
    alert("element is loaded.. do stuff");
}).catch(()=>{
    alert("element did not load in 3 seconds");
});

Versión 2020, con promesas

Este código sondeará el DOM 10 veces por segundo y resolverá una promesa si se encuentra antes del tiempo de espera opcional.

/**
 * Wait for an element before resolving a promise
 * @param {String} querySelector - Selector of element to wait for
 * @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout              
 */
function waitForElement(querySelector, timeout=0){
    const startTime = new Date().getTime();
    return new Promise((resolve, reject)=>{
        const timer = setInterval(()=>{
            const now = new Date().getTime();
            if(document.querySelector(querySelector)){
                clearInterval(timer);
                resolve();
            }else if(timeout && now - startTime >= timeout){
                clearInterval(timer);
                reject();
            }
        }, 100);
    });
}


waitForElement("#idOfElementToWaitFor", 3000).then(function(){
    alert("element is loaded.. do stuff");
}).catch(()=>{
    alert("element did not load in 3 seconds");
});

respuesta original

function waitForElement(id, callback){
    var poops = setInterval(function(){
        if(document.getElementById(id)){
            clearInterval(poops);
            callback();
        }
    }, 100);
}

waitForElement("idOfElementToWaitFor", function(){
    alert("element is loaded.. do stuff");
});

  • Genial! 😉 ¡TAN simple… pero efectivo! La única pieza de código funciona efectivamente en Angular: llamar a la función en ngAfterViewInit() ¡y eso es! ¡Gracias!

    – Pedro Ferreira

    29 oct 2019 a las 21:49


  • Me gustó su código 2020, ¿podría explicarme las declaraciones if dentro de la función waitForElement?

    – Mateo

    23 de agosto de 2020 a las 12:42

  • Ese es un código muy bueno, necesita alguna explicación, pero lo que hace este código es detectar si el elemento está cargado y luego hacer cosas, lo que este código no está haciendo es hacer cosas MIENTRAS se carga el elemento

    – Mateo

    23 de agosto de 2020 a las 12:44

  • @Mathew: eso es lo que hacen las promesas, te permiten hacer cosas mientras esperas otra cosa. los if declaración simplemente comprueba si el elemento existe todavía.

    – Una vez luché con un oso.

    24 de agosto de 2020 a las 14:41

  • @Una vez luché con un oso. estoy tratando de desactivar observer.disconnect() en MutationObserver para mantenerlo siempre en funcionamiento, pero no estoy pensando en hacerlo. ¿Cualquier pista?

    – LuisC

    28 sep a las 22:44


Avatar de usuario de Hatchet
Hacha

Suena como un trabajo para MutationObserver!

A MutationObserver es como un detector de eventos: puede adjuntarlo a cualquier elemento DOM para escuchar los cambios:

var observer = new MutationObserver(function (mutationRecords) {
    console.log("change detected");
});

La devolución de llamada se pasa una serie de MutationRecords, que contienen diferentes listas de nodos agregados/eliminados/modificados.

Luego, adjuntamos el observador a cualquier nodo:

observer.observe(document.body, {childList: true});
// second argument is a config: in this case, only fire the callback when nodes are added or removed

Nota: El soporte de IE no es sorprendente. (sorpresa, sorpresa) Solo funciona en IE 11. (Sin embargo, Edge lo admite).

Aquí hay otra pregunta SO sobre la detección de cambios en el DOM: Detectar cambios en el DOM

Retazo:

document.querySelector("button").addEventListener("click", function () {
  document.body.appendChild(document.createElement("span"));
});

var observer = new MutationObserver(function (m) {
  if (m[0].addedNodes[0].nodeName === "SPAN")
    document.querySelector("div").innerHTML += "Change Detected<br>";
});

observer.observe(document.body, {childList: true});
<button>Click</button>
<div></div>

Recomendaría no usar MutationObserver. Este enfoque tiene muchos inconvenientes: ralentiza la carga de la página, tiene poca compatibilidad con el navegador. Hay situaciones en las que este es el único enfoque para resolver el problema en cuestión.

Un mejor enfoque es utilizar el cargar evento DOM. Este evento funciona en etiquetas como iframe e img. Para otras etiquetas, este es el pseudocódigo en el que puede inspirarse:

<body>
<script>
function divLoaded(event){
  alert(event.target.previousSibling.previousSibling.id);
}
</script>
<div id="element_to_watch">This is an example div to watch it's loading</div>
<iframe style="display: none; width: 0; height: 0" onload="divLoaded(event)"/>
</body>

NOTA: El truco consiste en adjuntar un elemento iframe o img después de cada elemento que desee ver si se carga.

  • si sigue este enfoque, esta pregunta específica se puede resolver de muchas maneras, como puede observar la carga de la etiqueta del script y luego cargar dinámicamente otro script usando javascript.

    – Shyam Swaroop

    27 de febrero de 2020 a las 19:49

Aquí hay una función escrita en ClojureScript que invoca alguna función cada vez que se cumple una condición.

(require '[cljs.core.async :as a :refer [<!]]
         '[cljs-await.core :refer [await]])
(require-macros '[cljs.core.async.macros :refer [go]])

(defn wait-for-condition
  [pred-fn resolve-fn & {:keys [reject-fn timeout frequency]
                         :or {timeout 30 frequency 100}}]

  (let [timeout-ms (atom (* timeout 1000))]
    ;; `def` instead of `let` so it can recurse
    (def check-pred-satisfied
      (fn []
        (swap! timeout-ms #(- % frequency))
        (if (pred-fn)
          (resolve-fn)
          (if (pos? @timeout-ms)
            (js/setTimeout check-pred-satisfied frequency)
            (when reject-fn
              (reject-fn))))))

    (go (<! (await (js/Promise. #(js/setTimeout check-pred-satisfied frequency)))))))

Entonces podrías hacer algo como

wait_for_condition((id) => document.getElementById(id), () => alert("it happened"))

o

(wait-for-condition #(js/document.getElementById id)
                    #(js/alert "it happened")
                    :frequency 250
                    :timeout 10)

Avatar de usuario de Abhinandan Mittal
Abhinandan Mittal

Ampliando la solución de @i-wrestled-a-bear-once. Con este código js, ​​también podemos deshabilitar la ventana emergente cuando el componente de destino ya no está presente en el DOM.

Se puede usar para los casos en los que solicita la entrada de un archivo para una solicitud y no desea mostrar el archivo después de que haya terminado con la solicitud.

// Call this to run a method when a element removed from DOM
function waitForElementToUnLoad(querySelector) {
    const startTime = new Date().getTime();
    return new Promise((resolve, reject)=>{
        const timer = setInterval(()=>{
            const now = new Date().getTime();
            if(!document.querySelector(querySelector)) {
                clearInterval(timer);
                resolve();
            }
        }, 1000);
    });
}

// Call this to run a method when a element added to DOM, set timeout if required.
// Checks every second.
function waitForElementToLoad(querySelector, timeout=0) {
    const startTime = new Date().getTime();
    return new Promise((resolve, reject)=>{
        const timer = setInterval(()=>{
            const now = new Date().getTime();
            if(document.querySelector(querySelector)){
                clearInterval(timer);
                resolve();
            }else if(timeout && now - startTime >= timeout){
                clearInterval(timer);
                reject();
            }
        }, 1000);
    });
}

// Removing onclose popup if no file is uploaded.
function executeUnloadPromise(id) {
  waitForElementToUnLoad(id).then(function(){
      window.onbeforeunload = null;
      executeLoadPromise("#uploaded-file");
  });
}

// Adding onclose popup if a file is uploaded.
function executeLoadPromise(id) {
  waitForElementToLoad(id).then(function(){
      window.onbeforeunload = function(event) { 
        return "Are you sure you want to leave?"; 
      }
      executeUnloadPromise("#uploaded-file");
  });
}

executeLoadPromise("#uploaded-file");

Me encantaría sugerencias sobre cómo puedo hacer esto mejor.

¿Ha sido útil esta solución?