通过 wOxxOm 对 DOMNodeInserted 的替代方案的提示和 skyline3000 的回答,我开发了此任务解决方案的两种方法。第一种方法速度很快,但在触发onceAppended
之前它有大约 25 毫秒的延迟。callback
第二种方法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://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);
}
方法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://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
});
}
这两种方法的用法相同,只是函数名不同:
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);