我正在Chrome中开发一个扩展,我想知道:当一个元素出现时,最好的方法是什么?使用纯javascript,间隔检查,直到一个元素存在,或jQuery有一些简单的方法来做到这一点?


当前回答

我采取@Yong Wong的解决方案,但它有一个可选的超时,你可以指定根节点从哪里你想等待元素。

完整的异步/等待。

const $ = (selector, opts) => {
  let timeout = undefined;
  let root = undefined;

  if (opts) {
    ({ root, timeout } = opts);
  }

  if (root === undefined) root = document.body;
  
  const nodeFound = root.querySelector(selector);
  if (nodeFound) return new Promise(resolve => resolve(nodeFound));

  return new Promise((resolve, reject) => {
    let callback = () => {
      observer.disconnect();
    };

    const _resolve = (node) => {
      callback();
      resolve(node);
    };

    const _reject = (err) => {
      callback();
      reject(err);
    };

    if (timeout && timeout > 0) {
      const handle = setTimeout(() => {
        _reject(new Error("Element not found: timeout exceeded."));
      }, timeout);
      callback = () => {
        observer.disconnect();
        clearTimeout(handle);
      };
    }

    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const addedNode of mutation.addedNodes) {
          if (addedNode.matches(selector)) {
            _resolve(addedNode);
            return;
          }
        }
      }
    });

    observer.observe(root, {
      childList: true,
      subtree: true,
    });
  });
}

示例调用:

// wait for 10 seconds for 'div.bla-bla-bla' to appear as a child of 'div.some-container'
await $("div.bla-bla-bla", {
  timeout: 10000,
  root: document.querySelector("div.some-container") 
});

其他回答

更新

下面是一个更新的版本,可以使用承诺。如果达到特定的尝试次数,它也会“停止”。

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

用法很简单,用await使用它只是确保你在一个 异步功能:

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

过时的版本

只需添加所需的选择器。一旦找到元素,就可以在回调函数中访问它。

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

这是写在王勇答案(最高分答案)上面的一个更好的版本。

增加的特性:您可以等待一个元素特定的时间,精确定位,以提高性能。

async function waitForElement(selector, timeout = null, location = document.body) {
    return new Promise((resolve) => {
        let element = document.querySelector(selector);
        if (element) {
            return resolve(element);
        }

        const observer = new MutationObserver(async () => {
            let element = document.querySelector(selector);
            if (element) {
                resolve(element);
                observer.disconnect();
            } else {
                if (timeout) {
                    async function timeOver() {
                        return new Promise((resolve) => {
                            setTimeout(() => {
                                observer.disconnect();
                                resolve(false);
                            }, timeout);
                        });
                    }
                    resolve(await timeOver());
                }
            }
        });

        observer.observe(location, {
            childList: true,
            subtree: true,
        });
    });
}

用法:

await waitForElement(".nav-alt", 500, ".main-body")

奖励:等待一个元素从DOM中消失。

async function waitForElementDeath(selector, location = document.body) {
    return new Promise((resolve) => {
        const observer = new MutationObserver(async () => {
            if (!document.querySelector(selector)) {
                resolve(true);
                observer.disconnect();
            }
        });

        observer.observe(location, {
            childList: true,
            subtree: true,
        });
    });
}

用法:

await waitForElementDeath(".Popup-div", "Popup-Container")

我认为仍然没有任何答案在这里与简单易读的工作实例。使用MutationObserver接口来检测DOM的变化,如下所示:

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>

注意:关于MutationObserver的西班牙语Mozilla文档更多 详情,如果你想了解更多信息。

下面是一个函数,充当MutationObserver的薄包装器。唯一的要求是浏览器支持MutationObserver;不依赖于JQuery。运行下面的代码片段以查看一个工作示例。

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>

这里有一个用Javascript编写的promise返回解决方案(没有混乱的回调)。默认情况下,它每200ms检查一次。

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