我正在开发一个JavaScript模块,它对将要使用它的环境一无所知。
从技术上讲,我想实现下一个功能:
onceAppended(element, callback);
element
是HTMLElement
,并且在模块初始化期间该元素的父元素可能是未知的。 callback
是一个功能,必须在页面上显示element
后触发。
如果元素附加到文档,则必须立即调用回调。如果尚未追加element
,则只要文档中出现callback
,函数就会触发element
。
问题是,我们可以使用element
突变事件检测DOMNodeInserted
追加事件。但突变事件现在是deprecated。似乎MutationObserver
无法处理此任务,是吗?
这是我的代码段:
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?
}
提前感谢您对此有任何帮助!
答案 0 :(得分:2)
很遗憾没有办法与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()
,只需取出第二项检查参考数组中是否存在该元素。
答案 1 :(得分:0)
通过wOxxOm关于https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D和skyline3000的答案的提示,我开发了两种方法来完成此任务解决方案。第一种方法#line1/script#
很快,但在触发onceAppended
之前有一个约25毫秒的延迟。第二种方法在插入元素后立即触发callback
,但是当应用程序中附加了许多元素时,它可能会很慢。
此解决方案可在Alternative to DOMNodeInserted和GitHub ES6 模块中使用。以下是两种解决方案的简单代码。
callback
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://stackoverflow.com/questions/38588741/having-a-reference-to-an-element-how-to-detect-once-it-appended-to-the-document (StackOverflow 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_APPENDED{from{}to{}}`;
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);
}
这两种方法具有相同的用法,仅在函数名称中有所不同:
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://stackoverflow.com/questions/38588741/having-a-reference-to-an-element-how-to-detect-once-it-appended-to-the-document (StackOverflow 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
});
}