6

Ich bin ein JavaScript-Modul zu entwickeln, die nichts über die Umgebung kennt, in dem sie verwendet werden inHaben Sie einen Verweis auf ein Element, wie erkennt man, sobald es an das Dokument angehängt wurde? .

Und technisch gesehen, will ich die nächste Funktion implementieren:

onceAppended(element, callback); 

element ist ein HTMLElement und das übergeordnete Element dieses Elements kann während der Modulinitialisierung unbekannt sein. callback ist eine Funktion, die einmal ausgelöst werden muss, sobald auf der Seite element erscheint.

Callback muss sofort aufgerufen werden, wenn das Element an das Dokument angehängt wird. Falls element noch nicht angehängt ist, löst die Funktion callback aus, sobald element im Dokument erscheint.

Das Problem ist, können wir erkennen element Ereignis anhängen mit DOMNodeInserted Mutationsereignis. Aber Mutationsereignisse sind jetzt deprecated. Und es scheint, dass MutationObserver diese Aufgabe nicht bewältigen kann, oder?

Hier ist mein Code-Schnipsel:

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? 
} 

Sie für jede Hilfe zu diesem Thema Vielen Dank im Voraus!

+0

@wOxxOm Könnten Sie bitte meine Funktion vervollständigen, falls Sie das Thema kennen? Ich habe versucht, dies mit MutationObserver zu implementieren, und habe keine Ergebnisse erhalten. – ZitRo

+0

Ich sehe nicht, warum MutationObserver diese Aufgabe nicht bewältigen kann. Was hast du probiert? Ich denke, du musst den Beobachter anhängen, um jeden hinzugefügten Knoten zu dokumentieren und zu überprüfen. Es wird jedoch sehr ineffektiv sein. Vielleicht können Sie 'appendChild',' replaceChild' und andere relevante Funktionen in den Prototypen von HTMLElement und Node überschreiben. – wOxxOm

+0

Siehe auch [Alternative zu DOMNodeInserted] (http://StackOverflow.com/a/10343915) – wOxxOm

Antwort

0

Mit einem Tipp von wOxxOm über die Alternative to DOMNodeInserted und die Antwort von skyline3000 habe ich zwei Methoden dieser Aufgabenlösung entwickelt. Die erste Methode onceAppended ist schnell, hat aber eine Verzögerung von etwa 25 ms, bevor callback ausgelöst wird. Die zweite Methode löst direkt nach dem Einfügen des Elements callback aus, kann jedoch langsam sein, wenn viele Elemente in der Anwendung angehängt werden.

Die Lösung ist verfügbar unter GitHub und als npmES6 Modul. Im Folgenden finden Sie den einfachen Code der beiden Lösungen.

Methode 1 (mit CSS-Animationen)

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); 

} 

Methode 2 (mit 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 
    }); 

} 

Beide dieser Methoden hat die gleiche Verwendung, die nur in Funktionsnamen unterscheidet:

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); 
2

Leider gibt es keine Möglichkeit, diese genau die gleiche Art und Weise zu tun, wie mit DOMNodeInserted, weil keiner der MutationObserver Ereignisse, die Sie sagen, wenn ein Eltern Element ändert.

Stattdessen müssen Sie den Beobachter auf document.body platzieren und jeden Knoten überprüfen, der angehängt wird. Wenn Sie Ihren Callback immer dann ausführen möchten, wenn Knoten hinzugefügt werden, ist das ganz einfach. Wenn Sie nur möchten, dass bestimmte Knoten angehängt werden, müssen Sie einen Verweis auf diese Knoten haben.

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>' 

Wie Sie der Rückruf für Knoten ausgelöst sehen kann, wird überall angehängt innerhalb document.body. Wenn Sie möchten, dass callback() ausgeführt wird, wenn ein Element angehängt wird, nehmen Sie einfach die zweite Überprüfung heraus, wenn das Element in Ihrem Referenz-Array vorhanden ist.

+0

Danke! Diese Methode ist gut genug, um die Aufgabe zu lösen, aber wie auch immer, es ist ein großer Nachteil ist die Leistung. Wenn tausende Elemente zur Seite hinzugefügt werden (wie zum Beispiel eine neue Tabelle), wird der Callback tausend Mal aufgerufen. – ZitRo