Lassen Sie die Funktion warten, bis das Element vorhanden ist

158

Ich versuche, eine Zeichenfläche über einer anderen Zeichenfläche hinzuzufügen. Wie kann ich diese Funktion warten lassen, bis die erste Zeichenfläche erstellt wird?

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
quelle
4
Wenn Sie die Leinwand beim Klicken erstellen, führen Sie die Funktion aus oder lösen Sie ein Ereignis aus, das einen Handler ausführt, der die Funktion ausführt. Es gibt kein integriertes browserübergreifendes Ereignis, das auftritt, wenn ein Element verfügbar wird.
Kevin B
mögliches Duplikat von Wie warten, bis ein Element existiert?
user2284570

Antworten:

301

Wenn Sie Zugriff auf den Code haben, mit dem die Zeichenfläche erstellt wird, rufen Sie die Funktion einfach direkt nach der Erstellung der Zeichenfläche auf.

Wenn Sie keinen Zugriff auf diesen Code haben (z. B. wenn es sich um einen Code eines Drittanbieters wie Google Maps handelt), können Sie die Existenz in einem Intervall testen:

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

Beachten Sie jedoch, dass Code von Drittanbietern häufig die Option bietet, Ihren Code (durch Rückruf oder Ereignisauslösung) zu aktivieren, wenn das Laden abgeschlossen ist. Hier können Sie Ihre Funktion platzieren. Die Intervalllösung ist wirklich eine schlechte Lösung und sollte nur verwendet werden, wenn nichts anderes funktioniert.

Iftah
quelle
perfekte lösung für den einsatz in eckigen typen. Danke, dass du mich in die richtige Richtung geführt hast!
JuanTrev
1
Hervorragende Lösung, um darauf zu warten, dass etwas von Ajax erstellt wird, bevor Sie etwas anderes hinzufügen. Vielen Dank.
Countzero
@iftah Wie würde ich das zum Laufen bringen, wenn der Selektor eine Variable ist? Auch wenn es sich um eine ID handelt, ändert sich auch die Auswahl der Klasse. Manchmal werden mehrere Elemente zurückgegeben, wenn ich mit einer Klasse auswähle, und ich müsste einen Weg finden, einen Index an den Selektor zu übergeben, um herauszufinden, welches. Wie würde ich das machen? Danke
Kragalon
@Kraglon Dies ist eine völlig andere Frage und nicht für Kommentare dieser Antwort geeignet. Ich schlage vor, Sie stellen eine neue Frage, erklären, was Sie versucht haben, was das Problem ist, etc ...
Iftah
8
Eine weitere Sache ist wichtig zu erwähnen, wenn Sie die angegebene Lösung verwenden. Sie sollten diesen Code in einer for-Schleife haben und einen maximalen Wiederholungszähler festlegen. Wenn etwas schief geht, erhalten Sie keine Endlosschleife :)
BJ
48

Je nachdem, welchen Browser Sie unterstützen müssen, gibt es die Option MutationObserver .

BEARBEITEN: Alle gängigen Browser unterstützen jetzt MutationObserver .

Etwas in dieser Richtung sollte den Trick tun:

// 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 Ich habe diesen Code nicht selbst getestet, aber das ist die allgemeine Idee.

Sie können dies problemlos erweitern, um nur den Teil des DOM zu durchsuchen, der sich geändert hat. Verwenden Sie dazu das mutationsArgument, es ist ein Array von MutationRecordObjekten.

verdammt
quelle
2
Liebte diesen. Danke dir.
Insign
1
Dieses Muster ist in vielen Fällen sehr hilfreich, insbesondere wenn Sie JS auf eine Seite ziehen und nicht wissen, ob andere Elemente geladen sind.
Für den Namen
1
Die beste Antwort! Vielen Dank!
Antony Hatchkins
1
Ich habe einen alten Browser (ff38) und das hat mich gerettet.
Jung Rhew
1
Das ist großartig! Ich wünschte, ich wüsste, dass dies früher existiert.
Rob
39

Dies funktioniert nur mit modernen Browsern, aber ich finde es einfacher, nur einen zu verwenden. thenBitte testen Sie zuerst, aber:

Code

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

Oder mit Generatorfunktionen

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

Verwendung

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});
Jamie Hutber
quelle
Es gibt einen Fehler. Bei Verwendung von Generatorfunktionen sollte der querySelector in jeder Schleife aktualisiert werden:while (document.querySelector(selector) === null) {await rafAsync()}
haofly
31

Ein moderner Ansatz zum Warten auf Elemente:

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

Beachten Sie, dass dieser Code in eine asynchrone Funktion eingeschlossen werden muss .


quelle
4
das ist ziemlich ordentlich!
Dexter Bengil
Was ist rda
Daniel Möller
Na gut, aber woher kommt es? Was tut es? An was schickst du setTimeout?
Daniel Möller
@ DanielMöller Möglicherweise müssen Sie sich Promises ansehen , um diesen Code besser zu verstehen. Grundsätzlich richtet der Code hier ein Timeout von 500 ms ein und wartet, bis es abgeschlossen ist, bevor eine neue Iteration der while-Schleife gestartet wird. Clevere Lösung!
ClementParis016
8

Hier ist eine kleine Verbesserung gegenüber Jamie Hutbers Antwort

const checkElement = async selector => {

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

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

Ist besser weiterzuleiten requestAnimationFrameals in a setTimeout. Dies ist meine Lösung in es6-Modulen und mit Promises.

es6, Module und Versprechen:

// 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
quelle
Uncaught TypeError: Cannot read property 'then' of undefined
Jeff Puckett
Ich glaube, ich habe eine Rückkehr verpasst ... vor dem neuen Versprechen.
Ncubica
1
Dies ist die richtige Lösung, viel besser als alle regelmäßigen Timer-basierten Überprüfungen.
András Szepesházi
4
Tatsächlich funktioniert dies in der aktuellen Form nicht. Wenn $ someElement anfänglich null ist (dh noch nicht im DOM vorhanden ist), übergeben Sie diesen Nullwert (anstelle des CSS-Selektors) an Ihre onElementReady-Funktion, und das Element wird niemals aufgelöst. Übergeben Sie stattdessen den CSS-Selektor als Text und versuchen Sie, bei jedem Durchgang über .querySelector auf das Element zu verweisen.
András Szepesházi
1
Dies hat für meinen Anwendungsfall hervorragend funktioniert und scheint mir eine richtige Lösung zu sein. Danke
rdhaese
5

Hier ist eine Lösung mit Observablen.

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

Jetzt kannst du schreiben

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

Sie können überprüfen, ob der Dom bereits vorhanden ist, indem Sie ein Timeout festlegen, bis er bereits im Dom gerendert ist.

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

Eine weitere Variante von 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);

Nur für den Fall, dass das Element nie angezeigt wird, überprüfen wir es nicht unendlich.

Heriberto Perez
quelle
2

Wenn Sie eine generische Lösung mit MutationObserver wünschen, können Sie diese Funktion verwenden

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

Quelle: https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
Beispiel für die Verwendung

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

Hinweis: MutationObserver bietet eine hervorragende Browserunterstützung. https://caniuse.com/#feat=mutationobserver

Et voilà! :) :)

rdhainaut
quelle