如何等待直到元素存在?


236

我正在开发Chrome中的扩展程序,我想知道:找出元素何时存在的最佳方法是什么?使用纯JavaScript,间隔检查直到元素存在,或者jQuery是否有一些简单的方法来执行此操作?


1
看起来今天这里的每个选项(包括注释)都已过时或不完整。他们没有完全考虑@hughsk的出色输入,即兼容性参数。同时,我认为,对于一般的简单性和减少的开销风险,我建议仅对Ryan的答案使用Brandon的更新,我想。
cregox 2015年

4
MutationObserver> DOM Mutation Events> setTimeout
mattsven 2015年

2
不站在我的立场。setTimeout具有兼容性,易于实施,易于维护且开销可忽略不计。
cregox

setTimeoutjQuery我认为+ 不理想,原因有两个:1.)jQuery膨胀2.)您不必要地在DOM中手动查询元素,事件在速度上很容易击败,3.)它总是比任何本机都要慢实施。如果您需要基于某个元素的存在而相当快地执行任何操作,尤其是如果您希望获得无缝的用户体验,那就太差了。
mattsven 2015年

3
有3种人:可以计数的人和不能计数的人。; P
cregox 2015年

Answers:


148

DOMNodeInserted由于性能问题,不建议将其与其他DOM突变事件一起弃用-推荐的方法是使用MutationObserver来监视DOM。不过,仅在较新的浏览器中才支持该功能,因此您应该DOMNodeInsertedMutationObserver不可用时退回。

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

50
我总是发现MutationObserver api有点复杂,所以我建立了一个库Arcade.js,以提供一个更简单的api来侦听元素的创建/删除。
Uzair Farooq 2014年

15
我建议使用@UzairFarooq优秀库github.com/uzairfarooq/arrive
Dennis,

3
需要注意两件事:(1)最好这样做,if (mutation.addedNodes.length)因为if (mutation.addedNodes)即使它是一个空数组,它仍然会返回true。(2)您不能这样做,mutation.addedNodes.forEach()因为addNodes是一个nodeList,并且不能使用forEach遍历nodeList。有关此问题的解决方案,请参见toddmotto.com/ditch-the-array-foreach-call-nodelist-hack
thdoan 2015年

3
您能举一个例子说明如何使用它吗?当DOM元素存在时,不确定将我的jquery选择器或要执行的代码放在哪里。
Superdooperhero

1
@Superdooperhero我用一个简单的例子回答了。核实。stackoverflow.com/a/57395241/6542186
SilverSurfer,

113

我遇到了同样的问题,所以我继续为它编写了一个插件

$(selector).waitUntilExists(function);

码:

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

5
谢谢你的插件。我分叉并改进了一点。随时从我的更新中获取您想要的任何内容。我还计划了一些其他改进:更新的插件
Brandon Belvin 2013年

8
如果没有jquery dep,那就太好了...;)
knutole 2013年

4
也许您应该提到它是如何工作的:每500毫秒询问该元素是否存在(使用window.setInterval)。我不知道MutationObserver答案是否也可以通过投票来解决……
运动

2
如果该元素已经在页面上,它将无法正常工作。这是此功能的正确版本:gist.github.com/PizzaBrandon/5709010
RolandSoós16

2
您能否解释一下;函数(;(function ($, window) {)开头的用途?
2016年

76

这是一个等待元素显示的核心JavaScript函数。

参数:

  1. selector:此函数查找元素$ {selector}
  2. time:此函数每隔$ {time}毫秒检查此元素是否存在。

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

例如,设置selector="#div1"time=5000将查找id="div1"每5000毫秒的HTML标签。


真好!您可以编写此代码以便接受任何选择器吗?
mattsven

我怀疑我能做到这一点。但是,请看看这篇文章,以获得getElementByXpath:stackoverflow.com/questions/10596417/...
艾蒂安Tonnelier酒店


1
您可以编写它来使用变异观察器吗?
SuperUberDuper

还是您可以重写这个以使用诺言?
SuperUberDuper

25

您可以侦听DOMNodeInsertedDOMSubtreeModified在将新元素添加到DOM时触发的事件。

还有一个LiveQuery jQuery插件,它将检测何时创建新元素:

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

1
很好的插件!直接在jQuery中有类似的功能吗?我想知道没有现有功能可以做到这一点。如果这是THE插件,请对这个答案投票;)对我来说,它非常有效。非常感谢你。
塞缪尔

1
注意IE 9实现了DOMNodeInserted,但是有一个主要的错误,即当您暂时添加元素时(大多数情况下您想使用它),它将不会触发。详细信息在:help.dottoro.com/ljmcxjla.php
mikemaccana 2012年

23

我使用这种方法等待一个元素出现,以便在此之后可以执行其他功能。

假设doTheRestOfTheStuff(parameters)函数仅应在具有ID的元素the_Element_ID出现或加载完成后才能调用,我们可以使用,

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

21

你可以做

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

});

请注意,只有当从服务器请求元素时该元素存在于DOM中时,这才起作用。如果该元素是通过JavaScript动态添加的,则它将无法正常工作,您可能需要查看其他答案。


7
.ready()函数适用于大多数事物(如果不是任何事物),而不仅仅是document。它即使在上也无法与动态创建的元素一起使用.live()
理查德·尼尔·伊拉根

7
正如Richard指出的那样,@Bery仅适用于第一次从服务器请求时HTML中已经存在的元素。如果使用Javascript将元素动态添加到DOM,则它不起作用。
Chandranshu

6
@Sam,能否请您说明如何将其附加到内存中元素的引用?
维卡斯·辛哈尔

3
这个答案是不正确的。您实际上在这里检查的是常规内容$(document).ready(),而不是您认为同样适用的元素。这就是这个特殊的侦听器的工作方式。示例
Shikkediel '16


14

我认为,这里没有简单易懂的工作示例的答案。使用MutationObserver interface 来检测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>

注意: 如果您需要更多信息,则西班牙语Mozilla文档MutationObserver会更详细。


2
考虑发表评论以解释拒绝投票的原因,以便我改善答案。谢谢。
SilverSurfer

12

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

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

2
PossessWithin同意,这是一个非常干净的解决方案,对我有用。
jstafford

3
此答案适用于IE8-10以及现代浏览器。主要问题是,如果该元素不存在,它将继续运行-因此,当您确定该元素将在那里时,这是最好的方法。否则,您可以添加一个计数器。
命名

1
完美的工作对我来说
詹姆斯·斯图尔特

1
像魅力一样工作!
阿曼

1
它们是相似的,而不是相同的。此外,许多人正在这样做。最后,我自己编写了此解决方案的代码。但这是一个不充分的理由,但是,即使确实如此,我也很乐意告诉我一句话。答案解决了OP的问题,并且没有明显的动机。
迭戈·福特斯

11

对于使用jQuery的简单方法,我发现它可以很好地工作:

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

在这里,我们仅每500毫秒检查一次元素是否已加载,可以使用它。

这对于将单击处理程序添加到已动态添加到文档的元素中特别有用。


8

如何插入查询库?

insertationQuery使用附加到指定选择器的CSS动画回调,以在创建元素时运行回调。此方法允许在元素创建时(不仅是第一次)运行回调。

来自github:

捕获节点出现的非主事件方式。它使用选择器。

这不仅是为了获得更广泛的浏览器支持,在某些方面,它可能比DOMMutationObserver更好。

为什么?

  • 由于DOM事件会降低浏览器的速度,而insertQuery不会
  • 因为DOM Mutation Observer对浏览器的支持少于insertQuery
  • 因为有了insertingQuery,您可以使用选择器过滤DOM更改,而不会增加性能开销!

广泛的支持!

IE10 +,以及几乎所有其他内容(包括移动设备)


7

这是一个充当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>


6

这是使用原始Javascript的Promise返回解决方案(没有混乱的回调)。默认情况下,它每200毫秒检查一次。

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

5

这是一个纯Javascript函数,可让您等待任何事情。设置更长的时间间隔可以减少CPU资源。

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

要调用此功能(例如在jQuery中),请使用类似以下内容的代码:

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

3

返回a Promise并允许使用超时的解决方案(兼容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 Element) {
                clearInterval(interval);

                resolve();
            }

            now = performance.now();

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

对于多个元素(类型为NodeList):

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

例子:

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

适用于元素列表和单个元素。


1
我最喜欢的解决方案!为什么要检查element instanceof HTMLElement?可以不是nullHTMLElement吗?
Leeroy

1
您提出了一个有趣的观点。我应该通过使用Element代替(固定的)来扩大它的范围。我只是做了检查,因为我想确保变量element有属性innerHTML元素MDN文档状态。如果您不关心它,请随时将其删除!
Anwar

2

使用MutationObserver的更干净的示例:

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

2

对于那些习惯于承诺但不想使用任何第三方库或计时器的人来说,这是一个简单的解决方案。

我已经在我的项目中使用了一段时间

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)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });

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

要使用它:

waitForElm('.some-class').then(elm => console.log(elm.textContent));

或使用异步/等待

const elm = await waitForElm('.some-classs')

这很整齐!关于它的最酷的部分是,您也可以将它与async/ 一起使用await。您可能还可以通过这样做提高性能mutations.addedNodes.find(node => node.matchesSelector("..."))
mattsven

@mattsven好点!仅检查变异中的节点比执行document.querySelector更有效。
雍王

请更正拼写错误,从watiForElm到waitForElm
达维尔

1

如果您希望它在一段时间(超时)后停止寻找,则以下jQuery将起作用。它将在10秒后超时。我需要使用此代码而不是纯JS,因为我需要通过名称选择输入,并且在实现某些其他解决方案时遇到了麻烦。

 // 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

我通常将此代码段用于跟踪代码管理器:

<script>
(function exists() {
  if (!document.querySelector('<selector>')) {
    return setTimeout(exists);
  }
  // code when element exists
})();  
</script>

0

如果您有异步dom更改,此函数将检查DOM元素(以秒为单位的时间限制),对于DOM及其基于Promise的应用来说,它并不繁琐:)

function getElement(selector, i = 5) {
  return new Promise(async (resolve, reject) => {
    if(i <= 0) return reject(`${selector} not found`);
    const elements = document.querySelectorAll(selector);
    if(elements.length) return resolve(elements);
    return setTimeout(async () => await getElement(selector, i-1), 1000);
  })
}

// Now call it with your selector

try {
  element = await getElement('.woohoo');
} catch(e) { // catch the e }

//OR

getElement('.woohoo', 5)
.then(element => { // do somthing with the elements })
.catch(e => { // catch the error });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.