我正在Chrome中开发一个扩展,我想知道:当一个元素出现时,最好的方法是什么?使用纯javascript,间隔检查,直到一个元素存在,或jQuery有一些简单的方法来做到这一点?
当前回答
这里有一个用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);
}
}
});
}
其他回答
下面的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函数,它允许你等待任何事情。设置更长的间隔,以占用更少的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.");
});
你可以这样做
$('#yourelement').ready(function() {
});
请注意,这只在从服务器请求元素时元素出现在DOM中时才有效。如果元素是通过JavaScript动态添加的,那么它将不起作用,您可能需要查看其他答案。
受杰米·胡伯的启发,我想出了一个答案。
这是一个基于承诺的函数,你可以设置:
最大尝试次数-默认为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上的任何元素。
我采取@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")
});