Rendere la funzione attendere fino a quando l'elemento esiste


159

Sto provando ad aggiungere una tela su un'altra tela: come posso fare in modo che questa funzione attenda l'avvio fino alla creazione della prima tela?

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

4
Quando si crea il canvas al clic, eseguire la funzione o attivare un evento che esegue un gestore che esegue la funzione. non esiste alcun evento tra browser integrato che si verifica quando un elemento diventa disponibile.
Kevin B,

Risposte:


305

Se hai accesso al codice che crea il canvas: chiama semplicemente la funzione lì dopo aver creato il canvas.

Se non si ha accesso a quel codice (ad es. Se si tratta di un codice di terze parti come google maps), ciò che si potrebbe fare è verificare l'esistenza in un intervallo:

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

Ma nota: molte volte il codice di terze parti ha un'opzione per attivare il tuo codice (mediante callback o attivazione di eventi) al termine del caricamento. È qui che puoi mettere la tua funzione. La soluzione intervallo è davvero una cattiva soluzione e dovrebbe essere utilizzata solo se nient'altro funziona.


soluzione perfetta per l'uso in typeahead angularjs. Grazie per avermi guidato nella giusta direzione!
JuanTrev,

1
Ottima soluzione per aspettare che qualcosa venga creato dall'Ajax prima di inserire qualcos'altro. Molte grazie.
Countzero,

@iftah Come potrei farlo funzionare se il selettore è una variabile? Inoltre, se si tratta di un ID o cambia anche il selettore di classe. A volte ci sono più elementi restituiti quando seleziono con una classe e dovrei trovare un modo per passare un indice al selettore per capire quale. Come lo farei? Grazie
Kragalon

@Kraglon questa è una domanda completamente diversa e non adatta ai commenti di questa risposta. Ti suggerisco di fare una nuova domanda, spiegare cosa hai provato, qual è il problema, ecc ...
Iftah

8
Un'altra cosa importante da menzionare quando si utilizza la soluzione data, si dovrebbe avere quel pezzo di codice all'interno di un ciclo for e impostare un contatore di tentativi massimi, se qualcosa va storto non si finisce con un ciclo infinito :)
BJ

48

A seconda del browser che devi supportare, c'è l'opzione di MutationObserver .

EDIT: Tutti i principali browser ora supportano MutationObserver .

Qualcosa del genere dovrebbe fare il trucco:

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

NB Non ho testato questo codice da solo, ma questa è l'idea generale.

Puoi facilmente estenderlo per cercare solo la parte del DOM che è stata modificata. Per questo, usa l' mutationsargomento, è una matrice di MutationRecordoggetti.


2
Questo è adorabile. Grazie.
insegni il

1
Questo modello è davvero utile in molti casi, specialmente se stai trascinando JS in una pagina e non sai se vengono caricati altri elementi.
Per il nome

1
La migliore risposta! Grazie!
Antony Hatchkins,

1
Sono bloccato con un vecchio browser (ff38) e questo mi ha salvato.
jung rhew,

1
Questo è fantastico! Vorrei sapere che esisteva prima.
Rob

39

Funzionerà solo con i browser moderni, ma trovo più semplice utilizzare solo un, thenquindi prova prima ma:

Codice

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 le funzioni del generatore

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

C'è un errore. Quando si utilizzano le funzioni del generatore, querySelector deve essere aggiornato in ogni ciclo:while (document.querySelector(selector) === null) {await rafAsync()}
haofly

32

Un approccio più moderno all'attesa degli elementi:

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

Si noti che questo codice dovrebbe essere racchiuso in una funzione asincrona .


4
questo è abbastanza pulito!
Dexter Bengil,

Che cosa rc'è?
Daniel Möller

Bene, ok, ma da dove viene? Che cosa fa? A cosa stai spedendo setTimeout?
Daniel Möller

@ DanielMöller potresti dover dare un'occhiata a Promises per capire meglio questo codice. Fondamentalmente ciò che il codice fa qui è impostare un timeout di 500 ms e attendere il completamento prima di dare il via a una nuova iterazione del ciclo while. Soluzione intelligente!
ClementParis016,

8

Ecco un piccolo miglioramento rispetto alla risposta di Jamie Hutber

const checkElement = async selector => {

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

return document.querySelector(selector); };

8

È meglio inoltrare requestAnimationFrameche in a setTimeout. questa è la mia soluzione in moduli es6 e utilizzo Promises.

es6, moduli e promesse:

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

Uncaught TypeError: Cannot read property 'then' of undefined
Jeff Puckett,

Penso di aver perso un ritorno ... prima della nuova Promessa.
ncubica,

1
Questa è la soluzione corretta, molto meglio di tutti i controlli periodici basati su timer.
András Szepesházi,

4
In realtà, questo non funziona nella sua forma attuale. Se $ someElement è inizialmente nullo (cioè non ancora presente nel DOM), allora si passa questo valore null (invece del selettore CSS) alla funzione onElementReady e l'elemento non verrà mai risolto. Invece, passa il selettore CSS come testo e prova a ottenere riferimento all'elemento tramite .querySelector su ogni passaggio.
András Szepesházi,

1
Questo ha funzionato benissimo per il mio caso d'uso e mi sembra una soluzione adeguata. Grazie
rdhaese il

5

Ecco una soluzione che utilizza osservabili.

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

Adesso puoi scrivere

waitForElementToAppear(elementId).subscribe(el_ref => doSomethingWith(el_ref);

4

È possibile verificare se il dom esiste già impostando un timeout fino a quando non è già renderizzato nel dom.

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

3

Se si desidera una soluzione generica utilizzando MutationObserver è possibile utilizzare questa funzione

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

Fonte: https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
Esempio su come utilizzarlo

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

Nota: MutationObserver ha un ottimo supporto per il browser; https://caniuse.com/#feat=mutationobserver

Et voilà ! :)


2

Un'altra variante di 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);

Nel caso in cui l'elemento non sia mai mostrato, quindi non controlliamo all'infinito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.