我正在Chrome中开发一个扩展,我想知道:当一个元素出现时,最好的方法是什么?使用纯javascript,间隔检查,直到一个元素存在,或jQuery有一些简单的方法来做到这一点?
你可以这样做
$('#yourelement').ready(function() {
});
请注意,这只在从服务器请求元素时元素出现在DOM中时才有效。如果元素是通过JavaScript动态添加的,那么它将不起作用,您可能需要查看其他答案。
您可以监听DOMNodeInserted或DOMSubtreeModified事件,每当有新元素添加到DOM时,这些事件就会触发。
还有一个LiveQuery jQuery插件,它可以检测创建的新元素:
$("#future_element").livequery(function(){
//element created
});
我也有同样的问题,所以我继续写了一个插件。
$(选择).waitUntilExists(函数);
代码:
;(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));
由于性能问题,DOMNodeInserted和其他DOM突变事件已被弃用——推荐的方法是使用MutationObserver监视DOM。不过,它只在较新的浏览器中受支持,所以当MutationObserver不可用时,您应该回到DOMNodeInserted上。
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()
insertionQuery库呢?
insertionQuery使用CSS动画回调附加到指定的选择器,在创建元素时运行回调。此方法允许在创建元素时运行回调,而不仅仅是第一次。
从github:
用非dom事件的方式捕获节点。它使用选择器。 它不仅仅是为了更广泛的浏览器支持,在某些方面它可能比DOMMutationObserver更好。 为什么? 因为DOM事件会降低浏览器的速度,而insertionQuery不会 因为DOM Mutation Observer的浏览器支持比insertionQuery少 因为使用insertionQuery,您可以使用选择器过滤DOM更改,而没有性能开销! 广泛的支持! IE10+和其他设备(包括手机)
这里有一个核心JavaScript函数,用于等待元素的显示(好吧,将其插入到DOM中更准确)。
// 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);
}
})();
}
这个调用将每1000毫秒查找id="div1"的HTML标记。如果找到元素,它将显示一条警报消息Hi。如果在9000毫秒后没有找到任何元素,该函数将停止执行。
参数:
String:该函数查找元素${selector}。 callback: Function:这是一个函数,如果找到元素将被调用。 checkFrequencyInMs: Number:该函数每${checkFrequencyInMs}毫秒检查该元素是否存在。 timeoutInMs: Number:可选。该函数在${timeoutInMs}毫秒后停止查找元素。
注意:选择器的解释在https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
下面是一个函数,充当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>
我使用这种方法等待一个元素出现,这样我就可以在那之后执行其他函数。
让我们说doTheRestOfTheStuff(参数)函数应该只在ID为the_Element_ID的元素出现或完成加载后调用,我们可以使用,
var existCondition = setInterval(function() {
if ($('#the_Element_ID').length) {
console.log("Exists!");
clearInterval(existCondition);
doTheRestOfTheStuff(parameters);
}
}, 100); // check every 100ms
这是一个纯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.");
});
一个使用MutationObserver的更清晰的例子:
new MutationObserver( mutation => {
if (!mutation.addedNodes) return
mutation.addedNodes.forEach( node => {
// do stuff with node
})
})
对于一个使用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毫秒检查一次元素是否加载,当加载成功时,我们就可以使用它。
这对于向已动态添加到文档中的元素添加单击处理程序特别有用。
这里有一个用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);
}
}
});
}
一个返回承诺的解决方案,并允许使用超时(兼容IE 11+)。
对于单个元素(element类型):
"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
});
既适用于元素列表,也适用于单个元素。
更新
下面是一个更新的版本,可以使用承诺。如果达到特定的尝试次数,它也会“停止”。
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));
我认为仍然没有任何答案在这里与简单易读的工作实例。使用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文档更多 详情,如果你想了解更多信息。
如果你想让它在一段时间后停止查看(超时),那么下面的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);
我通常使用标签管理器的这个片段:
<script>
(function exists() {
if (!document.querySelector('<selector>')) {
return setTimeout(exists);
}
// code when element exists
})();
</script>
下面是一个使用MutationObserver api的简单解决方案。
没有jQuery 没有计时器 没有第三方库 基于Promise,并与async/await一起工作
我在几个项目中使用过它。
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('Element is ready');
console.log(elm.textContent);
});
或者使用async/await:
const elm = await waitForElm('.some-class');
你可以试试这个:
const wait_until_element_appear = setInterval(() => {
if ($(element).length !== 0) {
// some code
clearInterval(wait_until_element_appear);
}
}, 0);
这个办法对我很有效
受杰米·胡伯的启发,我想出了一个答案。
这是一个基于承诺的函数,你可以设置:
最大尝试次数-默认为10; 延迟(毫秒)-默认为100毫秒。
因此,默认情况下,它将等待1秒,直到元素出现在DOM上。
如果它没有出现,它将返回一个承诺。用null拒绝,这样您就可以按照您的愿望处理错误。
Code
export function _waitForElement(selector, delay = 10, 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);
}
}
用法:
async function wait(){
try{
const $el = await waitForElement(".llama");
console.log($el);
} catch(err){
console.error("Timeout - couldn't find element.")
}
}
wait();
在上面的例子中,它将等待选择器.llama。您可以添加更大的延迟,并在StackoverFlow的控制台上进行测试。
只需将类llama添加到DOM上的任何元素。
下面的observe函数将允许您通过选择器监听元素。
在下面的例子中,2秒过后,.greeting将被插入到.container中。因为我们正在监听这个元素的插入,所以我们可以有一个在插入时触发的回调。
const observe = (selector, callback, targetNode = document.body) => new MutationObserver(mutations => [...mutations] .flatMap((mutation) => [...mutation.addedNodes]) .filter((node) => node.matches && node.matches(selector)) .forEach(callback)) .observe(targetNode, { childList: true, subtree: true }); const createGreeting = () => { const el = document.createElement('DIV'); el.textContent = 'Hello World'; el.classList.add('greeting'); return el; }; const container = document.querySelector('.container'); observe('.greeting', el => console.log('I have arrived!', el), container); new Promise(res => setTimeout(() => res(createGreeting()), 2000)) .then(el => container.appendChild(el)); html, body { width: 100%; height: 100%; margin: 0; padding: 0; } body { display: flex; } .container { display: flex; flex: 1; align-items: center; justify-content: center; } .greeting { font-weight: bold; font-size: 2em; } <div class="container"></div>
更新
下面是一个实验性的async/await示例。
const sleep = (ms) => new Promise((res) => setTimeout(res, ms)); const observe = (selector, targetNode = document.body) => new Promise(res => { new MutationObserver(mutations => res([...mutations] .flatMap((mutation) => [...mutation.addedNodes]) .find((node) => node.matches && node.matches(selector)))) .observe(targetNode, { childList: true, subtree: true }); }); const createGreeting = () => { const el = document.createElement('DIV'); el.textContent = 'Hello World'; el.classList.add('greeting'); return el; }; const container = document.querySelector('.container'); observe('.greeting', container) .then(el => console.log('I have arrived!', el)); (async () => { await sleep(2000); container.appendChild(createGreeting()); })(); html, body { width: 100%; height: 100%; margin: 0; padding: 0; } body { display: flex; } .container { display: flex; flex: 1; align-items: center; justify-content: center; } .greeting { font-weight: bold; font-size: 2em; } <div class="container"></div>
简单的Javascript。
cont elementExist = setInterval(() => {
var elm = document.getElementById("elementId")
if (elm!=null)
// call your function here to do something
clearInterval(elementExist);
}
}, 100);
注意:这将阻塞其他执行
如果可以的话,我会尽量避开突变观察者,所以我想到了这个。它看起来类似于上面的一些其他答案。该函数将查找给定DOM调用中存在的第一个元素——className是预期的用法,但它也可以接受tagName或Id。如果您正在寻找具有给定类名或标记名的元素数量,则还可以为精确索引添加参数。
async function waitUntilElementExits(domkey,domquery,maxtime){
const delay = (ms) => new Promise(res => setTimeout(res, ms));
for(let i=0; i<maxtime; i=i+200){
await delay(200);
let elm = document[domkey](domquery);
if( (domkey == 'getElementById' && elm) || elm?.[0] ) break;
}
}
// usage
await waitUntilElementExits('getElementByClassName','some_class_name',10000)
这是写在王勇答案(最高分答案)上面的一个更好的版本。
增加的特性:您可以等待一个元素特定的时间,精确定位,以提高性能。
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")
你也可以使用getElementById代替querySelector
这个函数和 https://stackoverflow.com/a/61511955/10798137
async function waitForElementById(id, timeout = null, location = document.body) {
return new Promise((resolve) => {
let element = document.getElementById(id);
if (element) {
return resolve(element);
}
const observer = new MutationObserver(async () => {
let element = document.getElementById(id);
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,
});
});
使用它
waitForElement("tag_id", 500).then((elm) => {
console.log(elm)
})
Or
var elm = async waitForElement("tag_id", 500)
我采取@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")
});
推荐文章
- 如何使用Jest测试对象键和值是否相等?
- 将长模板文字行换行为多行,而无需在字符串中创建新行
- Chrome:网站使用HSTS。网络错误……这个页面稍后可能会工作
- 如何在JavaScript中映射/减少/过滤一个集?
- 什么是“升级-不安全-请求”HTTP报头?
- 如何验证一个XPath表达式在Chrome开发工具或Firefox的Firebug?
- Bower: ENOGIT Git未安装或不在PATH中
- HTML5文本区域占位符不出现
- 添加javascript选项选择
- 在Node.js中克隆对象
- 如何删除所有断点在谷歌Chrome一步?
- 我如何捕捉Ajax查询后错误?
- 为什么在JavaScript的Date构造函数中month参数的范围从0到11 ?
- 使用JavaScript更改URL参数并指定默认值
- jQuery:什么是限制“数字”仅输入文本框的最佳方法?(允许使用小数点)