Make function wait until element exists

317,226

Solution 1

If you have access to the code that creates the canvas - simply call the function right there after the canvas is created.

If you have no access to that code (eg. If it is a 3rd party code such as google maps) then what you could do is test for the existence in an interval:

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

But note - many times 3rd party code has an option to activate your code (by callback or event triggering) when it finishes to load. That may be where you can put your function. The interval solution is really a bad solution and should be used only if nothing else works.

Solution 2

Depending on which browser you need to support, there's the option of MutationObserver.

EDIT: All major browsers support MutationObserver now.

Something along the lines of this should do the trick:

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

N.B. I haven't tested this code myself, but that's the general idea.

You can easily extend this to only search the part of the DOM that changed. For that, use the mutations argument, it's an array of MutationRecord objects.

Solution 3

This will only work with modern browsers but I find it easier to just use a then so please test first but:

ES5

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

ES6

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

Usage

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});

Solution 4

A more modern approach to waiting for elements:

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

Note that this code would need to be wrapped in an async function.

Solution 5

Here's a minor improvement over Jamie Hutber's answer

const checkElement = async selector => {
  while ( document.querySelector(selector) === null) {
    await new Promise( resolve =>  requestAnimationFrame(resolve) )
  }
  return document.querySelector(selector); 
};

To use:

checkElement('.myElement').then((selector) => {
  console.log(selector);
});
Share:
317,226

Related videos on Youtube

Steven
Author by

Steven

Updated on February 01, 2022

Comments

  • Steven
    Steven over 2 years

    I'm trying to add a canvas over another canvas – how can I make this function wait to start until the first canvas is created?

    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;
        }
    
    • Kevin B
      Kevin B about 11 years
      When you create the canvas on click, run the function or trigger an event that runs a handler that runs the function. there is no built-in cross-browser event that happens when an element becomes available.
    • user2284570
      user2284570 over 9 years
      possible duplicate of How to wait until an element exists?
  • JuanTrev
    JuanTrev over 9 years
    perfect solution for use in angularjs typeahead. Thanks for guiding me in right direction!
  • Countzero
    Countzero over 8 years
    Excellent solution to wait for something being created by Ajax before putting some other thing in there. Thanks a lot.
  • Kragalon
    Kragalon about 8 years
    @iftah How would I get this to work if the selector is a variable? Also, if it is an ID or Class selector changes as well. Sometimes there are multiple elements returned when I select with a class, and I would need to find a way to pass an index to the selector to figure out which one. How would I do this? Thanks
  • Iftah
    Iftah about 8 years
    @Kraglon this is a completely different question and not suitable for comments of this answer. I suggest you ask a new question, explain what you tried, what the problem is, etc...
  • HussienK
    HussienK almost 8 years
    I definitely prefer this over setTimeout if I'm waiting for a value to change
  • B.J.
    B.J. over 7 years
    One more thing is important to mention when using the given solution, you should have that piece of code inside a for loop and set a maximum retry counter, if something goes wrong you dont end up with an infinity loop :)
  • Jeff Puckett
    Jeff Puckett over 6 years
    Uncaught TypeError: Cannot read property 'then' of undefined
  • ncubica
    ncubica over 6 years
    I think I missed a return... before new Promise.
  • András Szepesházi
    András Szepesházi about 6 years
    This is the proper solution, much better than all the periodic Timer-based checkings.
  • András Szepesházi
    András Szepesházi about 6 years
    Actually, this does not work in its current form. If $someElement is initially null (i.e. not yet present in the DOM), then you pass this null value (instead of the CSS selector) to your onElementReady function and the element will never be resolved. Instead, pass the CSS selector as text and try to get reference to the element via .querySelector on each pass.
  • Dexter Bengil
    Dexter Bengil over 5 years
    this is pretty neat!
  • insign
    insign about 5 years
    Loved this one. Thank you.
  • For the Name
    For the Name over 4 years
    This pattern is really helpful in a lot of instances, especially if you are pulling JS into a page and do not know if other items are loaded.
  • Antony Hatchkins
    Antony Hatchkins over 4 years
    The best answer! Thanks!
  • jung rhew
    jung rhew over 4 years
    I'm stuck with an old browser (ff38) and this saved me.
  • Rob
    Rob over 4 years
    This is amazing! I wish I knew this existed earlier.
  • hypers
    hypers about 4 years
    @B.J. is right. Without modification for max count this is a STACK BOMB!
  • Iftah
    Iftah about 4 years
    This is not a stack bomb, if the element never shows up then this just calls the function every 100ms (in this example). It is a waste of CPU cycles but it will not blow up.
  • haofly
    haofly about 4 years
    There is an error. When using generator functions, the querySelector should be updated in every loop: while (document.querySelector(selector) === null) {await rafAsync()}
  • Daniel Möller
    Daniel Möller about 4 years
    What is r there?
  • Daniel Möller
    Daniel Möller about 4 years
    Well, ok, but where does it come from? What does it do? What are you sending to setTimeout?
  • ClementParis016
    ClementParis016 about 4 years
    @DanielMöller you may need to take a look at Promises to get a better understanding of this code. Basically what the code does here is setting up a timeout of 500ms and wait for it to complete before kicking a new iteration of the while loop. Clever solution!
  • Jamie Hutber
    Jamie Hutber almost 4 years
    Can I ask why you would want to create so many variable assignments for the selector? The reason this is better afaik is it'll be faster, then having to check the selector every single time the animation frame is changed.
  • ncubica
    ncubica over 3 years
    @AndrásSzepesházi thtat's quite simple to fix pass a function instead of the element replace if ($element) for if (getElement()) then doesn't matter if the element is null or not at the beginning the spirit of the solution is the same. That doesn't change anything from the answer.
  • Volomike
    Volomike over 3 years
    Isn't Observable a React component? The question was about Javascript and jQuery.
  • Volomike
    Volomike over 3 years
    Great answer. Least lines of code. This answer doesn't watch the clock and slow the page down tremendously -- instead, it uses requestAnimationFrame. Editing now to show how to use it.
  • FrankL
    FrankL over 3 years
    Observable is the core type of rxjs. See rxjs-dev.firebaseapp.com/guide/overview
  • user890332
    user890332 over 3 years
    Where do you define your element? "this" doesn't exist yet until element is present.
  • user11809641
    user11809641 about 3 years
    IMHO this is better than the other answers and uses Promises, which are more performant than setInterval. +1
  • John Muggins
    John Muggins over 2 years
    This is perfect when running looping code in the Chrome console. It cuts my run time to about a third by not needing to use generic await 10 seconds here or 30 seconds there for fluctuating load times. And another note, if you are running looping code in the Chrome console then it doesn't need to be in an async function. You just place the above code in the place where you need to pause until the element is present. I don't know about other browsers. I only changed it to getElementById in stead of general querySelector.
  • Mecanik
    Mecanik over 2 years
  • Mike M
    Mike M over 2 years
    Hello Mecanik, sorry, I have accepted an edit proposal without testing it, my fault...Now I have edited the answer and anything works correctly.