代码之家  ›  专栏  ›  技术社区  ›  Jason Bunting

如何确定动态创建的DOM元素是否已添加到DOM中?

  •  41
  • Jason Bunting  · 技术社区  · 17 年前

    According to spec ,只有 BODY FRAMESET 元素提供了一个“onload”事件来附加,但我想知道何时在JavaScript中将动态创建的DOM元素添加到DOM中。

    我目前使用的超级天真的启发式方法如下:

    • 穿越 返回节点 返回元素的属性,直到我找到最终的祖先(即parentNode.parentNode.parent.etc,直到parentNode为null)

    • 如果最终祖先有一个定义的、非空的 身体 财产

      • 假设所讨论的元素是dom的一部分
    • 其他的

      • 在100毫秒内再次重复这些步骤

    我所追求的要么是确认我所做的是足够的(同样,它在IE7和FF3中都有效),要么是一个更好的解决方案,无论出于什么原因,我都完全没有注意到;也许我应该检查其他属性等。


    编辑:我想要一种与浏览器无关的方式来实现这一点,不幸的是,我并不生活在一个只使用一个浏览器的世界里;也就是说,我们感谢您提供特定于浏览器的信息,但请注意您知道哪个浏览器的信息 工作。谢谢!

    11 回复  |  直到 17 年前
        1
  •  30
  •   Community Mohan Dere    9 年前

    更新:对于任何对此感兴趣的人来说,这是我最终使用的实现:

    function isInDOMTree(node) {
       // If the farthest-back ancestor of our node has a "body"
       // property (that node would be the document itself), 
       // we assume it is in the page's DOM tree.
       return !!(findUltimateAncestor(node).body);
    }
    function findUltimateAncestor(node) {
       // Walk up the DOM tree until we are at the top (parentNode 
       // will return null at that point).
       // NOTE: this will return the same node that was passed in 
       // if it has no ancestors.
       var ancestor = node;
       while(ancestor.parentNode) {
          ancestor = ancestor.parentNode;
       }
       return ancestor;
    }
    

    我想要这个的原因是提供一种合成 onload DOM元素的事件。这是该函数(尽管我使用的是略有不同的东西,因为我将其与 MochiKit ):

    function executeOnLoad(node, func) {
       // This function will check, every tenth of a second, to see if 
       // our element is a part of the DOM tree - as soon as we know 
       // that it is, we execute the provided function.
       if(isInDOMTree(node)) {
          func();
       } else {
          setTimeout(function() { executeOnLoad(node, func); }, 100);
       }
    }
    

    例如,此设置可以按如下方式使用:

    var mySpan = document.createElement("span");
    mySpan.innerHTML = "Hello world!";
    executeOnLoad(mySpan, function(node) { 
       alert('Added to DOM tree. ' + node.innerHTML);
    });
    
    // now, at some point later in code, this
    // node would be appended to the document
    document.body.appendChild(mySpan);
    
    // sometime after this is executed, but no more than 100 ms after,
    // the anonymous function I passed to executeOnLoad() would execute
    

    希望这对某人有用。

    注意:我最终选择这个解决方案而不是 Darryl's answer 因为只有在同一文档中,getElementById技术才有效;我在一个页面上有一些iframe,页面之间以一些复杂的方式进行通信——当我尝试这样做时,问题是它找不到元素,因为它与它正在执行的代码属于不同的文档。

        2
  •  21
  •   Chris Calo SheetJS    13 年前

    最直接的答案是利用 Node.contains 方法,由Chrome、Firefox(Gecko)、Internet Explorer、Opera和Safari支持。以下是一个示例:

    var el = document.createElement("div");
    console.log(document.body.contains(el)); // false
    document.body.appendChild(el);
    console.log(document.body.contains(el)); // true
    document.body.removeChild(el);
    console.log(document.body.contains(el)); // false
    

    理想情况下,我们会使用 document.contains(el) ,但这在IE中不起作用,所以我们使用 document.body.contains(el) .

    不幸的是,您仍然需要轮询,但检查文档中是否存在元素非常简单:

    setTimeout(function test() {
      if (document.body.contains(node)) {
        func();
      } else {
        setTimeout(test, 50);
      }
    }, 50);
    

    如果您可以在页面中添加一些CSS,这里有另一种使用动画来检测节点插入的巧妙技术: http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

        3
  •  8
  •   Darryl Hein IrishChieftain    17 年前

    你不能做一个 document.getElementById('newElementId'); 看看是否返回true。如果没有,就像你说的,等待100毫秒,然后再试一次?

        4
  •  8
  •   ydaniv    12 年前

    只需使用 element.ownerDocument . 请看这里: https://developer.mozilla.org/en-US/docs/DOM/Node.ownerDocument 并执行以下操作:

    element.ownerDocument.body.contains(element)
    

    你很好。

        5
  •  2
  •   Leo    17 年前

    A. MutationObserver 您应该使用它来检测元素何时被添加到DOM中。 MutationObserver现在在所有现代浏览器(Chrome 26+、Firefox 14+、IE11、Edge、Opera 15+等)中都得到了广泛支持。

    这里有一个简单的例子,说明如何使用 MutationObserver 以监听何时将元素添加到DOM中。

    为简洁起见,我使用jQuery语法构建节点并将其插入DOM。

    var myElement = $("<div>hello world</div>")[0];
    
    var observer = new MutationObserver(function(mutations) {
       if (document.contains(myElement)) {
            console.log("It's in the DOM!");
            observer.disconnect();
        }
    });
    
    observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
    
    $("body").append(myElement); // console.log: It's in the DOM!
    

    这个 observer 每当从中添加或删除任何节点时,事件处理程序都会触发 document 在处理程序内部,我们执行 contains 检查以确定 myElement 现在在 文件 .

    您不需要迭代存储在中的每个MutationRecord mutations 因为你可以执行 document.contains 直接检查 myElement .

    为了提高性能,请更换 文件 包含特定元素 myElement 在DOM中。

        6
  •  2
  •   Borgar    17 年前

    您可以查询 document.getElementsByTagName(“*”).length 或者创建一个自定义appendChild函数,如下所示:

    var append = function(parent, child, onAppend) {
      parent.appendChild(child);
      if (onAppend) onAppend(child);
    }
    
    //inserts a div into body and adds the class "created" upon insertion
    append(document.body, document.createElement("div"), function(el) {
      el.className = "created";
    });
    

    更新

    根据要求,将我的评论中的信息添加到我的帖子中

    有一个 comment by John Resig 在Ajaxian上的Peppy库上,这似乎表明他的Sizzle库可能能够处理DOM插入事件。我很好奇是否也有处理IE的代码

    根据轮询的想法,我读到一些元素属性在元素被附加到文档之前是不可用的(例如element.scrullTop),也许你可以轮询它,而不是进行所有的DOM遍历。

    最后一件事:在IE中,一种可能值得探索的方法是使用onpropertychange事件。我认为将其附加到文档中必然会触发至少一个属性的该事件。

        7
  •  1
  •   eyelidlessness    17 年前

    在一个完美的世界里,你可以捕捉到突变事件。我怀疑它们即使在标准浏览器上也能可靠地工作。听起来你已经实现了一个变异事件,所以你可以添加它来使用原生变异事件,而不是在支持这些事件的浏览器上进行超时轮询。

    我见过通过比较来监视更改的DOM更改实现 document.body.innerHTML 上次保存时间 .innerHTML ,这并不完全优雅(但有效)。由于您最终将检查是否已添加特定节点,因此最好只检查每个中断。

    我能想到的改进是 使用 .offsetParent 而不是 .parentNode 因为这可能会让一些父母脱离你的圈子 (见评论)。或使用 compareDocumentIndex() 除了IE和测试 .sourceIndex IE的属性(如果节点不在DOM中,则应为-1)。

    这也可能有所帮助: X browser compareDocumentIndex implementaion by John Resig .

        8
  •  1
  •   Elliot B.    8 年前

    你想要 DOMNodeInserted 事件(或 DOMNodeInsertedIntoDocument ).

    编辑:IE完全有可能不支持这些事件,但我现在无法测试。

        9
  •  0
  •   nik    16 年前

    这是从jQuery代码中提取的问题的另一个解决方案。 以下函数可用于检查节点是否连接到DOM或是否“断开连接”。

    function isDisconnected( node ) {
        return !node || !node.parentNode || node.parentNode.nodeType === 11;
    }
    

    nodeType===11定义了一个DOCUMENT_FRAGMENT_NODE,它是一个未连接到文档对象的节点。

    有关更多信息,请参阅John Resig的以下文章: http://ejohn.org/blog/dom-documentfragments/

        10
  •  0
  •   slm Vikas Hardia    13 年前
    var finalElement=null; 
    

    我正在循环中构建dom对象,当循环处于最后一个循环时 lastDomObject 然后创建:

     finalElement=lastDomObject; 
    

    左循环:

    while (!finalElement) { } //this is the delay... 
    

    下一个操作可以使用任何新创建的dom对象。它在第一次尝试中奏效了。

        11
  •  -1
  •   Sergey Ilinsky    17 年前

    没有一个 DOM节点已插入 DOM节点插入到文档中 IE支持事件。DOM的另一个缺少的特性是 compareDocument位置 设计用于执行其名称所暗示的功能。

    至于建议的解决方案,我认为没有更好的了(除非你做了一些肮脏的技巧,比如覆盖原生DOM成员实现)

    此外,更正问题的标题:动态创建的元素从创建之时起就是DOM的一部分,它可以附加到文档片段或另一个元素,但可能不会附加到文档。

    我也认为你描述的问题不是你现在遇到的问题,这个问题看起来更像是你问题的潜在解决方案之一。你能详细解释一下这个用例吗?