javascript - Hur man väntar tills ett element finns?


Jag arbetar på ett tillägg i Chrome, och jag undrar: hur tar jag reda på när ett element blir till? Med vanlig JavaScript, med ett intervall som kollar tills ett element finns, eller har jQuery något enkelt sätt att göra detta?

Svar #1

Här är en enkel lösning som använder MutationObserver API:et.

  1. Ingen jQuery
  2. Ingen Timer
  3. Inga tredje parts-bibliotek
  4. Promise-baserad och fungerar bra med async/await

Jag har använt det i flera projekt.

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                observer.disconnect();
                resolve(document.querySelector(selector));
            }
        });

        // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

För att använda det:

waitForElm('.some-class').then((elm) => {
    console.log('Element is ready');
    console.log(elm.textContent);
});

Eller med async/await:

const elm = await waitForElm('.some-class');

Svar #2

DOMNodeInserted håller på att avvecklas, tillsammans med de andra DOM-mutationshändelserna, på grund av prestandaproblem - det rekommenderade tillvägagångssättet är att använda en MutationObserver för att bevaka DOM:en. Det stöds dock endast i nyare webbläsare, så du bör falla tillbaka på DOMNodeInserted när MutationObserver inte är tillgänglig.

let observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (!mutation.addedNodes) return

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

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

// stop watching using:
observer.disconnect()

Svar #3

Här är en grundläggande JavaScript-funktion för att vänta på att ett element ska visas (eller snarare, dess införande i DOM:en).

// Call the below function
waitForElementToDisplay("#div1",function(){alert("Hi");},1000,9000);

function waitForElementToDisplay(selector, callback, checkFrequencyInMs, timeoutInMs) {
  var startTimeInMs = Date.now();
  (function loopSearch() {
    if (document.querySelector(selector) != null) {
      callback();
      return;
    }
    else {
      setTimeout(function () {
        if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
          return;
        loopSearch();
      }, checkFrequencyInMs);
    }
  })();
}

Detta anrop kommer att söka efter HTML-taggen vars id="div1" var 1000 millisekunder. Om elementet hittas, kommer det att visa ett alert-meddelande Hej. Om inget element hittas efter 9000 millisekunder, stoppar denna funktion sin körning.

Parametrar:

  1. selector: Sträng : Denna funktion letar efter elementet $ {selector}.
  2. callback: Funktion : Detta är en funktion som kommer att kallas om elementet hittas.
  3. checkFrequencyInMs: Nummer : Denna funktion kontrollerar om detta element existerar var $ {checkFrequencyInMs} millisekunder.
  4. timeoutInMs : Nummer : Valfritt. Denna funktion slutar söka efter elementet efter ${timeoutInMs} millisekunder.

NB : Selektorer förklaras på https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector

Svar #4

Jag hade samma problem, så jag gick vidare och skrev ett plugin för det.

$(selector).waitUntilExists(function);

Kod :

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

Svar #5

Jag använde detta tillvägagångssätt för att vänta på att ett element skulle dyka upp så att jag kan köra de andra funktionerna efter det.

Låt oss säga att doTheRestOfTheStuff(parameters)-funktionen bara ska kallas efter att elementet med ID the_Element_ID visas eller har laddats klart, kan vi använda,

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

Svar #6

Uppdatering

Nedan finns en uppdaterad version som fungerar med löften. Den "stoppar" också om ett specifikt antal försök uppnås.

function _waitForElement(selector, delay = 50, tries = 100) {
    const element = document.querySelector(selector);

    if (!window[`__${selector}`]) {
      window[`__${selector}`] = 0;
      window[`__${selector}__delay`] = delay;
      window[`__${selector}__tries`] = tries;
    }

    function _search() {
      return new Promise((resolve) => {
        window[`__${selector}`]++;
        setTimeout(resolve, window[`__${selector}__delay`]);
      });
    }

    if (element === null) {
      if (window[`__${selector}`] >= window[`__${selector}__tries`]) {
        window[`__${selector}`] = 0;
        return Promise.resolve(null);
      }

      return _search().then(() => _waitForElement(selector));
    } else {
      return Promise.resolve(element);
    }
  }

Användningen är mycket enkel, för att använda den med await se bara till att du befinner dig inom en async-funktion:

const start = (async () => {
  const $el = await _waitForElement(`.my-selector`);
  console.log($el);
})();

Föråldrad version

Lägg helt enkelt till den selektor du vill ha. När elementet hittas kan du få tillgång till det i återuppringningsfunktionen.

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

Svar #7

Jag tror att det fortfarande inte finns något svar här med ett enkelt och läsbart fungerande exempel. Använd MutationObserver interface för att upptäcka DOM-förändringar, så här:

var observer = new MutationObserver(function(mutations) {
    if ($("p").length) {
        console.log("Exist, lets do something");
        observer.disconnect(); 
        //We can disconnect observer once the element exist if we dont want observe more changes in the DOM
    }
});

// Start observing
observer.observe(document.body, { //document.body is node target to observe
    childList: true, //This is a must have for the observer with subtree
    subtree: true //Set to true if changes must also be observed in descendants.
});
            
$(document).ready(function() {
    $("button").on("click", function() {
        $("p").remove();
        setTimeout(function() {
            $("#newContent").append("<p>New element</p>");
        }, 2000);
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button>New content</button>
<div id="newContent"></div>

Observera: Spanska Mozilla-dokument om MutationObserver är mer detaljerade om du vill ha mer information.

Svar #8

Du kan göra

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

});

Observera att detta endast fungerar om elementet är närvarande i DOM när det begärs från servern. Om elementet läggs till dynamiskt via JavaScript kommer det inte att fungera och du kan behöva titta på de andra svaren.

Svar #9

Du kan lyssna på DOMNodeInserted eller DOMSubtreeModified-händelser som utlöses varje gång ett nytt element läggs till i DOM.

Det finns också LiveQuery jQuery-plugin som skulle upptäcka när ett nytt element skapas:

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

Svar #10

För ett enkelt tillvägagångssätt med jQuery har jag funnit att detta fungerar bra:

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

Här kontrollerar vi helt enkelt var 500:e millisekund om elementet har laddats, när det har gjort det kan vi använda det.

Detta är särskilt användbart för att lägga till klickhanterare på element som dynamiskt har lagts till i dokumentet.

Svar #11

Här är en funktion som fungerar som ett tunt omslag runt MutationObserver. Det enda kravet är att webbläsaren stöder MutationObserver; det finns inget beroende av JQuery. Kör snutten nedan för att se ett fungerande exempel.

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>

Svar #12

Vad sägs om insertionQuery-biblioteket?

insertionQuery använder CSS-animation-återanrop kopplade till den/de specificerade selektorerna för att köra ett återanrop när ett element skapas. Denna metod tillåter att återanrop körs varje gång ett element skapas, inte bara första gången.

Från github:

Icke-dom-händelse sätt att fånga noder som dyker upp. Och det använder selektorer.

Det är inte bara för bredare webbläsarstöd, det kan vara bättre än DOMMutationObserver för vissa saker.

Varför?

  • Eftersom DOM-händelser saktar ner webbläsaren och insertionQuery gör det inte
  • Eftersom DOM Mutation Observer har mindre webbläsarstöd än insertionQuery
  • Eftersom med insertionQuery kan du filtrera DOM-förändringar med hjälp av selektorer utan prestandapåverkan!

Bred support!

IE10+ och mest allt annat (inklusive mobiler)

Relaterade artiklar