9

私は、使用される環境について何も知らない JavaScript モジュールを開発しています。

そして、技術的に言えば、次の機能を実装したいと考えています。

onceAppended(element, callback);

elementは でHTMLElementあり、この要素の親は、モジュールの初期化中に不明な場合があります。ページに表示されcallbackたらトリガーする必要がある関数です。element

要素がドキュメントに追加された場合は、コールバックをすぐに呼び出す必要があります。elementがまだ追加されていない場合は、ドキュメントに表示されると関数がトリガーされcallbackますelement

問題は、ミューelementテーション イベントを使用して追加イベントを検出できることDOMNodeInsertedです。ただし、ミューテーション イベントは非推奨になりました。そして、それは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?
}
4

2 に答える 2

1

DOMNodeInserted の代替案に関する wOxxOm のヒントと、skyline3000 の回答を参考にして、このタスク ソリューションの 2 つの方法を開発しました。最初の方法onceAppendedは高速ですが、トリガーされるまでに約 25 ミリ秒の遅延がありcallbackます。callback2 番目のメソッドは、要素が挿入された直後にトリガーされますが、アプリケーションに多くの要素が追加されると遅くなる可能性があります。

このソリューションはGitHubで、npm ES6モジュールとして入手できます。以下は、2 つのソリューションの単純なコードです。

方法 1 (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);
于 2016-07-28T12:22:19.337 に答える