web-dev-qa-db-fra.com

Comment attendre qu'un élément existe?

Je travaille sur une extension dans Chrome et je me demande: quel est le meilleur moyen de savoir quand un élément existe? Utilisation de javascript simple, avec un intervalle qui vérifie jusqu'à ce qu'un élément existe, ou jQuery a-t-il un moyen simple de le faire?

185
mattsven

DOMNodeInserted est obsolète, ainsi que les autres événements de mutation DOM, en raison de problèmes de performances. L'approche recommandée consiste à utiliser un MutationObserver pour surveiller le DOM. Cependant, il n'est supporté que par les nouveaux navigateurs. Vous devriez donc vous replier sur DOMNodeInserted lorsque MutationObserver n'est pas disponible.

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (!mutation.addedNodes) return

    for (var i = 0; i < mutation.addedNodes.length; i++) {
      // do things to your newly added nodes here
      var node = mutation.addedNodes[i]
    }
  })
})

observer.observe(document.body, {
    childList: true
  , subtree: true
  , attributes: false
  , characterData: false
})

// stop watching using:
observer.disconnect()
119
hughsk

J'avais ce même problème, alors je suis allé de l'avant et j'ai écrit un plugin pour cela.

$(selector).waitUntilExists(function);

Code:

;(function ($, window) {

var intervals = {};
var removeListener = function(selector) {

    if (intervals[selector]) {

        window.clearInterval(intervals[selector]);
        intervals[selector] = null;
    }
};
var found = 'waitUntilExists.found';

/**
 * @function
 * @property {object} jQuery plugin which runs handler function once specified
 *           element is inserted into the DOM
 * @param {function|string} handler 
 *            A function to execute at the time when the element is inserted or 
 *            string "remove" to remove the listener from the given selector
 * @param {bool} shouldRunHandlerOnce 
 *            Optional: if true, handler is unbound after its first invocation
 * @example jQuery(selector).waitUntilExists(function);
 */

$.fn.waitUntilExists = function(handler, shouldRunHandlerOnce, isChild) {

    var selector = this.selector;
    var $this = $(selector);
    var $elements = $this.not(function() { return $(this).data(found); });

    if (handler === 'remove') {

        // Hijack and remove interval immediately if the code requests
        removeListener(selector);
    }
    else {

        // Run the handler on all found elements and mark as found
        $elements.each(handler).data(found, true);

        if (shouldRunHandlerOnce && $this.length) {

            // Element was found, implying the handler already ran for all 
            // matched elements
            removeListener(selector);
        }
        else if (!isChild) {

            // If this is a recurring search or if the target has not yet been 
            // found, create an interval to continue searching for the target
            intervals[selector] = window.setInterval(function () {

                $this.waitUntilExists(handler, shouldRunHandlerOnce, true);
            }, 500);
        }
    }

    return $this;
};

}(jQuery, window));
110
Ryan Lester

Voici une fonction JavaScript essentielle pour attendre l'affichage d'un élément.

Paramètres: 

  1. selector: Cette fonction recherche l'élément $ {selector}
  2. time: Cette fonction vérifie si cet élément existe toutes les $ {time} millisecondes.

    function waitForElementToDisplay(selector, time) {
            if(document.querySelector(selector)!=null) {
                alert("The element is displayed, you can put your code instead of this alert.")
                return;
            }
            else {
                setTimeout(function() {
                    waitForElementToDisplay(selector, time);
                }, time);
            }
        }
    

Par exemple, définir selector="#div1" et time=5000 cherchera la balise HTML dont id="div1" toutes les 5000 millisecondes.

53
Etienne Tonnelier

Vous pouvez écouter les événements DOMNodeInserted ou DOMSubtreeModified qui se déclenchent chaque fois qu'un nouvel élément est ajouté au DOM. 

Il existe également un plug-in LiveQuery jQuery permettant de détecter la création d’un nouvel élément:

$("#future_element").livequery(function(){
    //element created
});
24
serg

Tu peux faire

$('#yourelement').ready(function() {

});

Veuillez noter que cela ne fonctionnera que si l'élément est présent dans le DOM lorsqu'il est demandé au serveur. Si l'élément est ajouté dynamiquement via JavaScript, cela ne fonctionnera pas et vous devrez peut-être consulter les autres réponses.

20
Splynx

J'ai utilisé cette approche pour attendre qu'un élément apparaisse afin de pouvoir exécuter les autres fonctions par la suite.

Disons que la fonction doTheRestOfTheStuff(parameters) ne devrait être appelée qu'après que l'élément avec l'ID the_Element_ID apparaisse ou que le chargement est terminé, nous pouvons

var existCondition = setInterval(function() {
 if ($('#the_Element_ID').length) {
    console.log("Exists!");
    clearInterval(existCondition);
    doTheRestOfTheStuff(parameters);
 }
}, 100); // check every 100ms
14
prime

Pour une approche simple utilisant jQuery, j’ai trouvé que cela fonctionnait bien:

  // Wait for element to exist.
  function elementLoaded(el, cb) {
    if ($(el).length) {
      // Element is now loaded.
      cb($(el));
    } else {
      // Repeat every 500ms.
      setTimeout(function() {
        elementLoaded(el, cb)
      }, 500);
    }
  };

  elementLoaded('.element-selector', function(el) {
    // Element is ready to use.
    el.click(function() {
      alert("You just clicked a dynamically inserted element");
    });
  });

Ici, nous vérifions simplement toutes les 500 ms pour voir si l'élément est chargé. Quand il l'est, nous pouvons l'utiliser.

Ceci est particulièrement utile pour ajouter des gestionnaires de clic aux éléments qui ont été ajoutés dynamiquement au document.

8
Hedley Smith

Que diriez-vous de la bibliothèque insertionQuery

insertionQuery utilise les rappels Animation CSS attachés au (x) sélecteur (s) spécifié (s) pour exécuter un rappel lors de la création d'un élément. Cette méthode permet d'exécuter des rappels chaque fois qu'un élément est créé, pas seulement la première fois.

De github:

Manière non-dom-event pour attraper les nœuds qui apparaissent. Et il utilise des sélecteurs.

Ce n'est pas juste pour un support de navigateur plus large, il peut être meilleur que DOMMutationObserver pour certaines choses.

Pourquoi?

  • Parce que les événements DOM ralentissent le navigateur et InsertionQuery ne
  • Parce que DOM Mutation Observer prend moins en charge le navigateur que insertionQuery
  • Parce qu'avec insertionQuery, vous pouvez filtrer les modifications DOM à l'aide de sélecteurs sans perte de performance!

Support généralisé!

IE10 + et surtout autre chose (y compris le mobile)

8
b3wii

Voici une fonction qui agit comme une enveloppe mince autour de MutationObserver. La seule exigence est que le navigateur prenne en charge MutationObserver; il n'y a pas de dépendance à JQuery. Exécutez l'extrait ci-dessous pour voir un exemple de travail.

function waitForMutation(parentNode, isMatchFunc, handlerFunc, observeSubtree, disconnectAfterMatch) {
  var defaultIfUndefined = function(val, defaultVal) {
    return (typeof val === "undefined") ? defaultVal : val;
  };

  observeSubtree = defaultIfUndefined(observeSubtree, false);
  disconnectAfterMatch = defaultIfUndefined(disconnectAfterMatch, false);

  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.addedNodes) {
        for (var i = 0; i < mutation.addedNodes.length; i++) {
          var node = mutation.addedNodes[i];
          if (isMatchFunc(node)) {
            handlerFunc(node);
            if (disconnectAfterMatch) observer.disconnect();
          };
        }
      }
    });
  });

  observer.observe(parentNode, {
    childList: true,
    attributes: false,
    characterData: false,
    subtree: observeSubtree
  });
}

// Example
waitForMutation(
  // parentNode: Root node to observe. If the mutation you're looking for
  // might not occur directly below parentNode, pass 'true' to the
  // observeSubtree parameter.
  document.getElementById("outerContent"),
  // isMatchFunc: Function to identify a match. If it returns true,
  // handlerFunc will run.
  // MutationObserver only fires once per mutation, not once for every node
  // inside the mutation. If the element we're looking for is a child of
  // the newly-added element, we need to use something like
  // node.querySelector() to find it.
  function(node) {
    return node.querySelector(".foo") !== null;
  },
  // handlerFunc: Handler.
  function(node) {
    var elem = document.createElement("div");
    elem.appendChild(document.createTextNode("Added node (" + node.innerText + ")"));
    document.getElementById("log").appendChild(elem);
  },
  // observeSubtree
  true,
  // disconnectAfterMatch: If this is true the hanlerFunc will only run on
  // the first time that isMatchFunc returns true. If it's false, the handler
  // will continue to fire on matches.
  false);

// Set up UI. Using JQuery here for convenience.

$outerContent = $("#outerContent");
$innerContent = $("#innerContent");

$("#addOuter").on("click", function() {
  var newNode = $("<div><span class='foo'>Outer</span></div>");
  $outerContent.append(newNode);
});
$("#addInner").on("click", function() {
  var newNode = $("<div><span class='foo'>Inner</span></div>");
  $innerContent.append(newNode);
});
.content {
  padding: 1em;
  border: solid 1px black;
  overflow-y: auto;
}
#innerContent {
  height: 100px;
}
#outerContent {
  height: 200px;
}
#log {
  font-family: Courier;
  font-size: 10pt;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>Create some mutations</h2>
<div id="main">
  <button id="addOuter">Add outer node</button>
  <button id="addInner">Add inner node</button>
  <div class="content" id="outerContent">
    <div class="content" id="innerContent"></div>
  </div>
</div>
<h2>Log</h2>
<div id="log"></div>

5
Ivan Karajas

Voici une fonction Javascript pure qui vous permet d’attendre quoi que ce soit. Définissez un intervalle plus long pour utiliser moins de ressources processeur.

/**
 * @brief Wait for something to be ready before triggering a timeout
 * @param {callback} isready Function which returns true when the thing we're waiting for has happened
 * @param {callback} success Function to call when the thing is ready
 * @param {callback} error Function to call if we time out before the event becomes ready
 * @param {int} count Number of times to retry the timeout (default 300 or 6s)
 * @param {int} interval Number of milliseconds to wait between attempts (default 20ms)
 */
function waitUntil(isready, success, error, count, interval){
    if (count === undefined) {
        count = 300;
    }
    if (interval === undefined) {
        interval = 20;
    }
    if (isready()) {
        success();
        return;
    }
    // The call back isn't ready. We need to wait for it
    setTimeout(function(){
        if (!count) {
            // We have run out of retries
            if (error !== undefined) {
                error();
            }
        } else {
            // Try again
            waitUntil(isready, success, error, count -1, interval);
        }
    }, interval);
}

Pour appeler cela, par exemple dans jQuery, utilisez quelque chose comme:

waitUntil(function(){
    return $('#myelement').length > 0;
}, function(){
    alert("myelement now exists");
}, function(){
    alert("I'm bored. I give up.");
});
3
xgretsch

Voici une solution de retour de promesse en Javascript vanille (pas de callback désordonné). Par défaut, il vérifie toutes les 200 ms.

function waitFor(selector) {
    return new Promise(function (res, rej) {
        waitForElementToDisplay(selector, 200);
        function waitForElementToDisplay(selector, time) {
            if (document.querySelector(selector) != null) {
                res(document.querySelector(selector));
            }
            else {
                setTimeout(function () {
                    waitForElementToDisplay(selector, time);
                }, time);
            }
        }
    });
}
2
blaster

Un exemple plus propre utilisant MutationObserver:

new MutationObserver( mutation => {
    if (!mutation.addedNodes) return
    mutation.addedNodes.forEach( node => {
        // do stuff with node
    })
})
2
Zaz

Ajoutez simplement le sélecteur que vous voulez. Une fois que l'élément est trouvé, vous pouvez accéder à la fonction de rappel.

const waitUntilElementExists = (selector, callback) => {
const el = document.querySelector(selector);

if (el){
    return callback(el);
}

setTimeout(() => waitUntilElementExists(selector, callback), 500);
}

waitUntilElementExists('.wait-for-me', (el) => console.log(el));
0
PossessWithin

Si vous voulez qu'il arrête de chercher après un certain temps (timeout), alors le jQuery suivant fonctionnera. Il expirera après 10 secondes. J'avais besoin d'utiliser ce code plutôt que du pur JS car je devais sélectionner une entrée via name et j'avais du mal à mettre en œuvre certaines des autres solutions.

 // Wait for element to exist.

    function imageLoaded(el, cb,time) {

        if ($(el).length) {
            // Element is now loaded.

            cb($(el));

            var imageInput =  $('input[name=product\\[image_location\\]]');
            console.log(imageInput);

        } else if(time < 10000) {
            // Repeat every 500ms.
            setTimeout(function() {
               time = time+500;

                imageLoaded(el, cb, time)
            }, 500);
        }
    };

    var time = 500;

    imageLoaded('input[name=product\\[image_location\\]]', function(el) {

     //do stuff here 

     },time);
0
S-Thomas

Une solution renvoyant une Promise et permettant d’utiliser un délai d’expiration (compatible IE 11+):

"use strict";

function waitUntilElementLoaded(selector) {
    var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

    var start = performance.now();
    var now = 0;

    return new Promise(function (resolve, reject) {
        var interval = setInterval(function () {
            var element = document.querySelector(selector);

            if (element instanceof HTMLElement) {
                clearInterval(interval);

                resolve();
            }

            now = performance.now();

            if (now - start >= timeout) {
                reject("Could not find the element " + selector + " within " + timeout + " ms");
            }
        }, 100);
    });
}

"use strict";

function waitUntilElementsLoaded(selector) {
    var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

    var start = performance.now();
    var now = 0;

    return new Promise(function (resolve, reject) {
        var interval = setInterval(function () {
            var elements = document.querySelectorAll(selector);

            if (elements instanceof NodeList) {
                clearInterval(interval);

                resolve(elements);
            }

            now = performance.now();

            if (now - start >= timeout) {
                reject("Could not find elements " + selector + " within " + timeout + " ms");
            }
        }, 100);
    });
}

Exemples:

waitUntilElementLoaded('#message', 800).then(function(element) {
    // element found and available

    element.innerHTML = '...';
}).catch(function() {
    // element not found within 800 milliseconds
});

waitUntilElementsLoaded('.message', 10000).then(function(elements) {
    for(const element of elements) {
        // ....
    }
}).catch(function(error) {
    // elements not found withing 10 seconds
});

Fonctionne pour une liste d'éléments et un seul élément.

0
Anwar