有一个元素的引用,如何检测它何时附加到文档中?
Posted
技术标签:
【中文标题】有一个元素的引用,如何检测它何时附加到文档中?【英文标题】:Having a reference to an element, how to detect when it is appended to the document? 【发布时间】:2016-11-30 00:49:47 【问题描述】:我正在开发一个 javascript 模块,它对使用它的环境一无所知。
而且,从技术上讲,我想实现下一个功能:
onceAppended(element, callback);
element
是一个htmlElement
,并且在模块初始化期间该元素的父元素可能是未知的。 callback
是一个函数,必须在页面上出现element
时触发。
如果将元素附加到文档,则必须立即调用回调。如果element
尚未附加,则一旦element
出现在文档上,函数将触发callback
。
问题是,我们可以使用DOMNodeInserted
突变事件来检测element
追加事件。但是突变事件现在是deprecated。而且MutationObserver
好像也处理不了这个任务吧?
这是我的代码 sn-p:
function onceAppended (element, callback)
let el = element,
listener;
while (el.parentNode)
el = el.parentNode;
if (el instanceof Document)
callback();
return;
if (typeof MutationObserver === "undefined") // use deprecated method
element.addEventListener("DOMNodeInserted", listener = (ev) =>
if (ev.path.length > 1 && ev.path[ev.length - 2] instanceof Document)
element.removeEventListener("DOMNodeInserted", listener);
callback();
, false);
return;
// Can't MutationObserver detect append event for the case?
【问题讨论】:
@wOxxOm 如果您知道主题,请您完成我的功能吗?我尝试使用 MutationObservers 来实现这一点,但没有得到任何结果。 我不明白为什么 MutationObserver 不能处理这个任务。你试过什么?我想您必须将观察者附加到文档并检查每个添加的节点。不过,这将是非常无效的。所以也许你可以在 HTMLElement 和 Node 的原型中重写appendChild
、replaceChild
等相关函数。
另见Alternative to DOMNodeInserted
@wOxxOm 将观察者附加到文档是可能的解决方案之一。但是您可以想象它需要处理的事件数量。我正在寻找更好、对性能更友好的解决方案(如果有的话)。
@wOxxOm [UPD] 我相信,覆盖标准事件会产生副作用。
【参考方案1】:
不幸的是,没有办法以与DOMNodeInserted
完全相同的方式执行此操作,因为MutationObserver
事件都不会告诉您元素的父级何时更改。
相反,您必须将观察者放在document.body
上并检查每个附加的节点。如果您想在附加任何节点时运行回调,那很容易。如果您只希望它在附加某些节点时运行,那么您必须在某处保留对这些节点的引用。
let elements = [];
elements[0] = document.createElement('div');
elements[1] = document.createElement('span');
elements[2] = document.createElement('p');
elements[3] = document.createElement('a');
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
const observer = new MutationObserver(function(mutations)
// 'addedNodes' is an array of nodes that were appended to the DOM.
// Checking its length let's us know if we've observed a node being added
if (mutations[0].addedNodes.length > 0)
// 'indexOf' let's us know if the added node is in our reference array
if (Array.prototype.indexOf.call(mutations[0].addedNodes[0], elements) > -1)
// Run the callback function with a reference to the element
callback(mutations[0].addedNodes[0]);
);
observer.observe(document.body,
childList: true,
subtree: true
);
function callback(element)
console.log(element);
document.body.appendChild(elements[2]); // => '<p></p>'
elements[2].appendChild(elements[3]); // => '<a></a>'
如您所见,回调是针对附加在document.body
中的任何位置的节点触发的。如果您希望 callback()
在添加任何元素时运行,只需第二次检查该元素是否存在于您的引用数组中。
【讨论】:
谢谢!这种方法足以解决任务,但它的最大缺点是性能。将数千个元素添加到页面(例如形成一个新表),回调会被调用数千次。【参考方案2】:通过 wOxxOm 对Alternative to DOMNodeInserted 的提示和skyline3000 的回答,我开发了此任务解决方案的两种方法。第一种方法onceAppended
速度很快,但在触发callback
之前有大约25ms 的延迟。第二种方法在插入元素后立即触发callback
,但在应用程序中追加大量元素时可能会很慢。
该解决方案在GitHub 和npm ES6 模块中可用。下面是两种解决方案的纯代码。
方法一(使用 CSS 动画)
function useDeprecatedMethod (element, callback)
let listener;
return element.addEventListener(`DOMNodeInserted`, listener = (ev) =>
if (ev.path.length > 1 && ev.path[ev.length - 2] instanceof Document)
element.removeEventListener(`DOMNodeInserted`, listener);
callback();
, false);
function isAppended (element)
while (element.parentNode)
element = element.parentNode;
return element instanceof Document;
/**
* Method 1. Asynchronous. Has a better performance but also has an one-frame delay after element is
* appended (around 25ms delay) of callback triggering.
* This method is based on CSS3 animations and animationstart event handling.
* Fires callback once element is appended to the document.
* @author ZitRo (https://github.com/ZitRos)
* @see https://***.com/questions/38588741/having-a-reference-to-an-element-how-to-detect-once-it-appended-to-the-document (*** original question)
* @see https://github.com/ZitRos/dom-onceAppended (Home repository)
* @see https://www.npmjs.com/package/dom-once-appended (npm package)
* @param HTMLElement element - Element to be appended
* @param function callback - Append event handler
*/
export function onceAppended (element, callback)
if (isAppended(element))
callback();
return;
let sName = `animation`, pName = ``;
if ( // since DOMNodeInserted event is deprecated, we will try to avoid using it
typeof element.style[sName] === `undefined`
&& (sName = `webkitAnimation`) && (pName = "-webkit-")
&& typeof element.style[sName] === `undefined`
&& (sName = `mozAnimation`) && (pName = "-moz-")
&& typeof element.style[sName] === `undefined`
&& (sName = `oAnimation`) && (pName = "-o-")
&& typeof element.style[sName] === `undefined`
)
return useDeprecatedMethod(element, callback);
if (!document.__ONCE_APPENDED)
document.__ONCE_APPENDED = document.createElement('style');
document.__ONCE_APPENDED.textContent = `@$ pName keyframes ONCE_APPENDEDfromto`;
document.head.appendChild(document.__ONCE_APPENDED);
let oldAnimation = element.style[sName];
element.style[sName] = `ONCE_APPENDED`;
element.addEventListener(`animationstart`, () =>
element.style[sName] = oldAnimation;
callback();
, true);
方法2(使用MutationObserver)
function useDeprecatedMethod (element, callback)
let listener;
return element.addEventListener(`DOMNodeInserted`, listener = (ev) =>
if (ev.path.length > 1 && ev.path[ev.length - 2] instanceof Document)
element.removeEventListener(`DOMNodeInserted`, listener);
callback();
, false);
function isAppended (element)
while (element.parentNode)
element = element.parentNode;
return element instanceof Document;
/**
* Method 2. Synchronous. Has a lower performance for pages with a lot of elements being inserted,
* but triggers callback immediately after element insert.
* This method is based on MutationObserver.
* Fires callback once element is appended to the document.
* @author ZitRo (https://github.com/ZitRos)
* @see https://***.com/questions/38588741/having-a-reference-to-an-element-how-to-detect-once-it-appended-to-the-document (*** original question)
* @see https://github.com/ZitRos/dom-onceAppended (Home repository)
* @see https://www.npmjs.com/package/dom-once-appended (npm package)
* @param HTMLElement element - Element to be appended
* @param function callback - Append event handler
*/
export function onceAppendedSync (element, callback)
if (isAppended(element))
callback();
return;
const MutationObserver =
window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
if (!MutationObserver)
return useDeprecatedMethod(element, callback);
const observer = new MutationObserver((mutations) =>
if (mutations[0].addedNodes.length === 0)
return;
if (Array.prototype.indexOf.call(mutations[0].addedNodes, element) === -1)
return;
observer.disconnect();
callback();
);
observer.observe(document.body,
childList: true,
subtree: true
);
这两种方法用法相同,只是函数名不同:
import onceAppended from "dom-once-appended"; // or onceAppendedSync
function myModule ()
let sampleElement = document.createElement("div");
onceAppended(sampleElement, () => // or onceAppendedSync
console.log(`Sample element is appended!`);
);
return sampleElement;
// somewhere else in the sources (example)
let element = myModule();
setTimeout(() => document.body.appendChild(element), 200);
【讨论】:
以上是关于有一个元素的引用,如何检测它何时附加到文档中?的主要内容,如果未能解决你的问题,请参考以下文章
Python Selenium StaleElementReferenceException:消息:过时的元素引用:元素未附加到页面文档
如何让 pcp 自动将节点附加到 postgres pgpool?
C ++:在向量中插入啥 - 指针或引用,向量何时复制它的元素?