6

Zajmuję moduł JavaScript, który nic nie wie o środowisko, w którym będzie ono używane wPo odwołaniu do elementu, jak wykryć po dołączeniu do dokumentu? .

I, technicznie rzecz biorąc, chcę wdrożyć następną funkcję:

onceAppended(element, callback); 

element jest HTMLElement i element nadrzędny tego elementu może być nieznany podczas inicjowania modułu. callback jest funkcją, która musi zostać wywołana, gdy na stronie pojawi się element.

Oddzwonienie musi zostać wywołane natychmiast, jeśli element zostanie dołączony do dokumentu. W przypadku, gdy element nie jest jeszcze dołączony, funkcja uruchomi callback po pojawieniu się element na dokumencie.

Problem polega na tym, że możemy wykryć zdarzenie wstawienia element przy użyciu zdarzenia mutacji DOMNodeInserted. Ale wydarzenia związane z mutacją są teraz deprecated. I wydaje się, że MutationObserver nie może obsłużyć tego zadania, prawda?

Tu jest mój urywek kodu:

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

Z góry dziękuję za wszelką pomoc w tej sprawie!

+0

@wOxxOm Czy możesz wypełnić moją funkcję, jeśli znasz temat? Próbowałem zaimplementować to z MutationObservers i nie otrzymałem wyników. – ZitRo

+0

Nie widzę powodu, dla którego MutationObserver nie może obsłużyć tego zadania. Co próbujesz? Sądzę, że będziesz musiał dołączyć obserwatora do dokumentu i sprawdzić każdy dodany węzeł. Będzie to jednak bardzo nieskuteczne. Może więc możesz zastąpić 'appendChild',' replaceChild' i inne odpowiednie funkcje w prototypach HTMLElement i Node. – wOxxOm

+0

Zobacz także [Alternatywa do DOMNodeInserted] (http://stackoverflow.com/a/10343915) – wOxxOm

Odpowiedz

0

Podejmując wskazówkę wOxxOm na temat odpowiedzi Alternative to DOMNodeInserted i skyline3000, opracowałem dwie metody rozwiązania tego zadania. Pierwsza metoda jest szybka, ale ma opóźnienie około 25 ms przed wyzwoleniem . Druga metoda uruchamia callback zaraz po wstawieniu elementu, ale może być wolna, gdy wiele elementów zostanie dodanych do aplikacji.

Rozwiązanie jest dostępne w module GitHub oraz jako moduł npmES6. Poniżej znajduje się prosty kod obu rozwiązań.

Metoda 1 (z wykorzystaniem animacji 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); 

} 

Metoda 2 (przy użyciu 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 
    }); 

} 

Oba Metoda ta ma takie samo zastosowanie, który różni się tylko w nazwach funkcji:

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

Niestety nie ma sposobu, aby to zrobić dokładnie tak samo jak z DOMNodeInserted ponieważ żaden z wydarzeń MutationObserver powiedzieć, kiedy element jest rodzic zmienia.

Zamiast tego musisz umieścić obserwatora na document.body i sprawdzić każdy węzeł, który zostanie dołączony. Jeśli chcesz uruchomić swoje wywołanie zwrotne, gdy dołączony jest dowolny węzeł, to jest łatwe. Jeśli chcesz, aby uruchamiał się tylko wtedy, gdy pewne węzły są dołączone, będziesz musiał zachować odniesienie do tych węzłów gdzieś.

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

Jak widać wywołanie zwrotne jest wyzwalane dla węzłów załączonych w dowolnym miejscu document.body. Jeśli chcesz, aby callback() był uruchamiany po dodaniu dowolnego elementu, po prostu wykup drugie sprawdzenie, czy element istnieje w twojej tablicy referencyjnej.

+0

Dziękujemy! Ta metoda jest wystarczająco dobra, aby rozwiązać zadanie, ale jednak jest to duża wadą wydajności. Po dodaniu tysięcy elementów do strony (takich jak tworzenie nowej tabeli) wywołanie zwrotne jest wywoływane tysiąc razy. – ZitRo

Powiązane problemy