Hacer que la función espere hasta que exista el elemento

159

Estoy tratando de agregar un lienzo sobre otro lienzo: ¿cómo puedo hacer que esta función espere para comenzar hasta que se cree el primer lienzo?

function PaintObject(brush) {

    this.started = false;

    // get handle of the main canvas, as a DOM object, not as a jQuery Object. Context is unfortunately not yet
    // available in jquery canvas wrapper object.
    var mainCanvas = $("#" + brush).get(0);

    // Check if everything is ok
    if (!mainCanvas) {alert("canvas undefined, does not seem to be supported by your browser");}
    if (!mainCanvas.getContext) {alert('Error: canvas.getContext() undefined !');}

    // Get the context for drawing in the canvas
    var mainContext = mainCanvas.getContext('2d');
    if (!mainContext) {alert("could not get the context for the main canvas");}

    this.getMainCanvas = function () {
        return mainCanvas;
    }
    this.getMainContext = function () {
        return mainContext;
    }

    // Prepare a second canvas on top of the previous one, kind of second "layer" that we will use
    // in order to draw elastic objects like a line, a rectangle or an ellipse we adjust using the mouse
    // and that follows mouse movements
    var frontCanvas = document.createElement('canvas');
    frontCanvas.id = 'canvasFront';
    // Add the temporary canvas as a second child of the mainCanvas parent.
    mainCanvas.parentNode.appendChild(frontCanvas);

    if (!frontCanvas) {
        alert("frontCanvas null");
    }
    if (!frontCanvas.getContext) {
        alert('Error: no frontCanvas.getContext!');
    }
    var frontContext = frontCanvas.getContext('2d');
    if (!frontContext) {
        alert("no TempContext null");
    }

    this.getFrontCanvas = function () {
        return frontCanvas;
    }
    this.getFrontContext = function () {
        return frontContext;
    }
Steven
fuente
44
Cuando cree el lienzo al hacer clic, ejecute la función o active un evento que ejecute un controlador que ejecute la función. no hay un evento incorporado en el navegador que ocurre cuando un elemento está disponible.
Kevin B
posible duplicado de ¿Cómo esperar hasta que exista un elemento?
usuario2284570

Respuestas:

305

Si tiene acceso al código que crea el lienzo, simplemente llame a la función allí mismo después de crear el lienzo.

Si no tiene acceso a ese código (p. Ej., Si se trata de un código de un tercero, como Google Maps), lo que puede hacer es comprobar la existencia en un intervalo:

var checkExist = setInterval(function() {
   if ($('#the-canvas').length) {
      console.log("Exists!");
      clearInterval(checkExist);
   }
}, 100); // check every 100ms

Pero tenga en cuenta: muchas veces el código de terceros tiene una opción para activar su código (mediante devolución de llamada o activación de eventos) cuando termina de cargarse. Ahí es donde puedes poner tu función. La solución de intervalo es realmente una mala solución y debe usarse solo si nada más funciona.

Iftah
fuente
solución perfecta para usar en angularjs typeahead. ¡Gracias por guiarme en la dirección correcta!
JuanTrev
1
Excelente solución para esperar a que Ajax cree algo antes de poner algo más allí. Muchas gracias.
Countzero
@iftah ¿Cómo podría hacer que esto funcione si el selector es una variable? Además, si se trata de un ID o un selector de clase, también cambia. A veces hay múltiples elementos devueltos cuando selecciono con una clase, y necesitaría encontrar una manera de pasar un índice al selector para averiguar cuál. ¿Cómo haría esto? Gracias
Kragalon
@Kraglon esta es una pregunta completamente diferente y no es adecuada para comentarios de esta respuesta. Le sugiero que haga una nueva pregunta, explique lo que intentó, cuál es el problema, etc ...
Iftah
8
Es importante mencionar algo más cuando se usa la solución dada, debe tener ese fragmento de código dentro de un bucle for y establecer un contador de reintentos máximo, si algo sale mal, no terminará con un bucle infinito :)
BJ
48

Según el navegador que necesite admitir, existe la opción de MutationObserver .

EDITAR: Todos los principales navegadores admiten MutationObserver ahora .

Algo en este sentido debería hacer el truco:

// callback executed when canvas was found
function handleCanvas(canvas) { ... }

// set up the mutation observer
var observer = new MutationObserver(function (mutations, me) {
  // `mutations` is an array of mutations that occurred
  // `me` is the MutationObserver instance
  var canvas = document.getElementById('my-canvas');
  if (canvas) {
    handleCanvas(canvas);
    me.disconnect(); // stop observing
    return;
  }
});

// start observing
observer.observe(document, {
  childList: true,
  subtree: true
});

Nota: no he probado este código yo mismo, pero esa es la idea general.

Puede extender esto fácilmente para buscar solo la parte del DOM que cambió. Para eso, use el mutationsargumento, es una matriz de MutationRecordobjetos.

maldito
fuente
2
Me encantó este. Gracias.
insign
1
Este patrón es realmente útil en muchos casos, especialmente si está arrastrando JS a una página y no sabe si hay otros elementos cargados.
Para el nombre del
1
La mejor respuesta! ¡Gracias!
Antony Hatchkins
1
Estoy atascado con un navegador antiguo (ff38) y esto me salvó.
jung rhew
1
¡Esto es increíble! Ojalá supiera que esto existía antes.
Rob
39

Esto solo funcionará con los navegadores modernos, pero me resulta más fácil usar solo un, thenasí que prueba primero pero:

Código

function rafAsync() {
    return new Promise(resolve => {
        requestAnimationFrame(resolve); //faster than set time out
    });
}

function checkElement(selector) {
    if (document.querySelector(selector) === null) {
        return rafAsync().then(() => checkElement(selector));
    } else {
        return Promise.resolve(true);
    }
}

O usando funciones generadoras

async function checkElement(selector) {
    const querySelector = document.querySelector(selector);
    while (querySelector === null) {
        await rafAsync()
    }
    return querySelector;
}  

Uso

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});
Jamie Hutber
fuente
Hay un error. Cuando se utilizan funciones de generador, querySelector debe actualizarse en cada bucle:while (document.querySelector(selector) === null) {await rafAsync()}
desde el
32

Un enfoque más moderno para esperar elementos:

while(!document.querySelector(".my-selector")) {
  await new Promise(r => setTimeout(r, 500));
}
// now the element is loaded

Tenga en cuenta que este código debería incluirse en una función asíncrona .


fuente
44
esto es bastante bueno!
Dexter Bengil
Que rhay
Daniel Möller
Bueno, está bien, pero ¿de dónde viene? ¿Qué hace? ¿A qué estás enviando setTimeout?
Daniel Möller
@ DanielMöller, es posible que necesite consultar Promesas para comprender mejor este código. Básicamente, lo que hace el código aquí es configurar un tiempo de espera de 500 ms y esperar a que se complete antes de iniciar una nueva iteración del ciclo while. Solución inteligente!
ClementParis016
8

Aquí hay una pequeña mejora con respecto a la respuesta de Jamie Hutber

const checkElement = async selector => {

while ( document.querySelector(selector) === null) {
    await new Promise( resolve =>  requestAnimationFrame(resolve) )
}

return document.querySelector(selector); };
wLc
fuente
8

Es mejor retransmitir requestAnimationFrameque en a setTimeout. Esta es mi solución en módulos es6 y utilizando Promises.

es6, módulos y promesas:

// onElementReady.js
const onElementReady = $element => (
  new Promise((resolve) => {
    const waitForElement = () => {
      if ($element) {
        resolve($element);
      } else {
        window.requestAnimationFrame(waitForElement);
      }
    };
    waitForElement();
  })
);

export default onElementReady;

// in your app
import onElementReady from './onElementReady';

const $someElement = document.querySelector('.some-className');
onElementReady($someElement)
  .then(() => {
    // your element is ready
  }

plain js and promises:

var onElementReady = function($element) {
  return new Promise((resolve) => {
    var waitForElement = function() {
      if ($element) {
        resolve($element);
      } else {
        window.requestAnimationFrame(waitForElement);
      }
    };
    waitForElement();
  })
};

var $someElement = document.querySelector('.some-className');
onElementReady($someElement)
  .then(() => {
    // your element is ready
  });
ncubica
fuente
Uncaught TypeError: Cannot read property 'then' of undefined
Jeff Puckett el
Creo que me perdí un regreso ... antes de la nueva Promesa.
ncubica
1
Esta es la solución adecuada, mucho mejor que todas las comprobaciones periódicas basadas en temporizador.
András Szepesházi
44
En realidad, esto no funciona en su forma actual. Si $ someElement es inicialmente nulo (es decir, aún no está presente en el DOM), entonces pasa este valor nulo (en lugar del selector CSS) a su función onElementReady y el elemento nunca se resolverá. En su lugar, pase el selector CSS como texto e intente obtener una referencia al elemento a través de .querySelector en cada paso.
András Szepesházi
1
Esto funcionó muy bien para mi caso de uso y me parece una solución adecuada. Gracias
rdhaese
5

Aquí hay una solución usando observables.

waitForElementToAppear(elementId) {                                          

    return Observable.create(function(observer) {                            
            var el_ref;                                                      
            var f = () => {                                                  
                el_ref = document.getElementById(elementId);                 
                if (el_ref) {                                                
                    observer.next(el_ref);                                   
                    observer.complete();                                     
                    return;                                                  
                }                                                            
                window.requestAnimationFrame(f);                             
            };                                                               
            f();                                                             
        });                                                                  
}                                                                            

Ahora puedes escribir

waitForElementToAppear(elementId).subscribe(el_ref => doSomethingWith(el_ref);
FrankL
fuente
4

Puede verificar si el dom ya existe estableciendo un tiempo de espera hasta que ya se represente en el dom.

var panelMainWrapper = document.getElementById('panelMainWrapper');
setTimeout(function waitPanelMainWrapper() {
    if (document.body.contains(panelMainWrapper)) {
        $("#panelMainWrapper").html(data).fadeIn("fast");
    } else {
        setTimeout(waitPanelMainWrapper, 10);
    }
}, 10);
Carmela
fuente
3

Si desea una solución genérica usando MutationObserver, puede usar esta función

// MIT Licensed
// Author: jwilson8767

/**
 * Waits for an element satisfying selector to exist, then resolves promise with the element.
 * Useful for resolving race conditions.
 *
 * @param selector
 * @returns {Promise}
 */
export function elementReady(selector) {
  return new Promise((resolve, reject) => {
    const el = document.querySelector(selector);
    if (el) {resolve(el);}
    new MutationObserver((mutationRecords, observer) => {
      // Query for elements matching the specified selector
      Array.from(document.querySelectorAll(selector)).forEach((element) => {
        resolve(element);
        //Once we have resolved we don't need the observer anymore.
        observer.disconnect();
      });
    })
      .observe(document.documentElement, {
        childList: true,
        subtree: true
      });
  });
}

Fuente: https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
Ejemplo de cómo usarlo

elementReady('#someWidget').then((someWidget)=>{someWidget.remove();});

Nota: MutationObserver tiene una gran compatibilidad con el navegador; https://caniuse.com/#feat=mutationobserver

Et voilà! :)

rdhainaut
fuente
2

Otra variante de Iftah

var counter = 10;
var checkExist = setInterval(function() {
  console.log(counter);
  counter--
  if ($('#the-canvas').length || counter === 0) {
    console.log("by bye!");
    clearInterval(checkExist);
  }
}, 200);

En caso de que el elemento nunca se muestre, no lo comprobamos infinitamente.

heriberto perez
fuente