Как я могу определить, был ли добавлен динамически созданный элемент DOM в DOM?

Согласно спецификации, только элементы BODY и FRAMESET предоставляют событие «onload» для присоединения, но я хотел бы знать, когда динамически созданный элемент DOM был добавлен в DOM в JavaScript.

В настоящее время я использую сверхнаивные эвристики, которые работают, а именно:

  • Вернитесь к свойству parentNode элемента, пока я не найду окончательного предка (т.е. parentNode.parentNode.parentNode.etc, пока parentNode не станет нулевым)

  • Если конечный предок имеет определенное ненулевое свойство тело

    • Предположим, что рассматриваемый элемент является частью dom
  • else

    • повторите эти шаги еще раз через 100 миллисекунд

Мне нужно либо подтверждение того, что то, что я делаю, достаточно (опять же, это работает как в IE7, так и в FF3), либо лучшее решение, о котором по какой-то причине я совершенно не замечал; возможно, другие свойства, которые я должен проверить, и т. д.


Обновлено: мне нужен независимый от браузера способ сделать это, к сожалению, я не живу в мире с одним браузером; Тем не менее, информация, относящаяся к конкретному браузеру, приветствуется, но обратите внимание, в каком браузере вы знаете, что он делает работает. Спасибо!

Как вы можете не знать, когда в ваш документ добавляется какой-то контент? Вы не автор страницы?

Sergey Ilinsky 21.10.2008 03:08

@Sergey: Чем больше абстрагируешь, тем больше вероятность, что одна рука не знает, что делает другая.

eyelidlessness 21.10.2008 03:35

Точно - я создаю «виджет», который внутренне должен будет знать, когда он был добавлен в DOM, но поскольку я не обязательно являюсь потребителем, я не буду знать когда, потому что это определит клиентский код.

Jason Bunting 21.10.2008 07:26

Однако событие onload IMG не является частью спецификации W3C. Кроме того, мне нужна эта функциональность не только для изображений, поэтому она не принесла бы мне много пользы и, кроме того, была бы немного более хрупкой.

Jason Bunting 10.11.2008 18:10

В браузере есть метод определения того, находится ли один узел внутри другого: Node.contains. Добавил ответ ниже: stackoverflow.com/a/13725984/101869

Chris Calo 05.12.2012 18:59
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
40
5
28 000
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Вам нужно событие DOMNodeInserted (или DOMNodeInsertedIntoDocument).

Обновлено: вполне возможно, что эти события не поддерживаются IE, но я не могу это проверить прямо сейчас.

Спасибо - правильно ли я предполагаю, что это идиома, специфичная для Mozilla / Firefox / Gecko?

Jason Bunting 21.10.2008 02:50

Нет, у тебя нет на это права. Это стандартная идиома DOM-Events.

Sergey Ilinsky 21.10.2008 03:06

Понятно. Тем не менее, я не думаю, что IE (рыжий пасынок браузеров) его поддерживает.

Jason Bunting 22.10.2008 00:03

Можете ли вы сделать document.getElementById('newElementId'); и посмотреть, вернет ли это значение. Если нет, как вы говорите, подождите 100 мс и попробуйте еще раз?

Что делать, если у него нет идентификатора? Тогда не получится, а может и так. В противном случае хороший ответ ... может быть, я посмотрю, сработает ли это для некоторых из моих потребностей, и воспользуюсь другим методом, если у меня нет идентификатора.

Jason Bunting 21.10.2008 02:59

Почему бы просто не добавить идентификатор? Вы можете добавить уникальный идентификатор, используя какую-то случайную строку, а затем просто искать ее. Я знаю, что вам не нравится jQuery, но с jQuery вы можете легко найти его на основе, возможно, класса, родителя или ребенка.

Darryl Hein 21.10.2008 03:08

Правда, это может быть хорошим способом сделать это - мне придется немного подумать об этом ... спасибо. LOL @ мне не нравится jQuery. Мне это нравится, мне просто не нравится, когда люди думают, что это ответ на все вопросы о JavaScript. :)

Jason Bunting 21.10.2008 07:27

Так что мне это нравится и идеи Боргара тоже. Я мог бы проверить, есть ли у рассматриваемого элемента свойство ID; если это так, используйте это с getElementById, иначе создайте случайный, надеюсь, уникальный идентификатор и назначьте его; когда getElementById найдет его, удалите изначально несуществующий идентификатор и перейдите оттуда.

Jason Bunting 22.10.2008 01:05

Мне нравится эта идея. Это лаконично. Это практически уверенность в том, что document.getElementById () будет работать, если что-то делает. :-)

Borgar 22.10.2008 02:13

Верно то, что простота, согласованность и надежность во всех браузерах сделали бы это лучшим решением; Я действительно не хочу писать код для браузера, поэтому изменю утвержденный ответ.

Jason Bunting 22.10.2008 02:46

Ни одно из событий DOMNodeInserted или DOMNodeInsertedIntoDocument не поддерживается в IE. Другой отсутствующей в IE [only] функции DOM является функция compareDocumentPosition, которая предназначена для выполнения того, что предлагает ее название.

Что касается предлагаемого решения, я думаю, что нет лучшего (если вы не сделаете какие-то грязные трюки, такие как перезапись реализаций собственных членов DOM)

Также исправляем заголовок вопроса: динамически созданный элемент является частью DOM с момента его создания, он может быть добавлен к фрагменту документа или к другому элементу, но не может быть добавлен к документу.

Я также думаю, что описанная вами проблема - это не та проблема, которая у вас есть, это скорее одно из возможных решений вашей проблемы. Вы, наверное, можете подробно объяснить вариант использования?

Спасибо за ответ - проблема, которую я описал является как проблема, с которой я столкнулся, но спасибо за попытку прочитать мои мысли в любом случае. :) Я абстрагировался от деталей моей проблемы до такой степени, что ответа на этот вопрос будет достаточно для решения моей более конкретной проблемы.

Jason Bunting 22.10.2008 00:48

Вы можете запросить 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";
});

Обновлять

По запросу добавление информации из моих комментариев в свой пост

Сегодня в библиотеке Peppy на Ajaxian был комментарий Джона Ресига, который, казалось, предполагал, что его библиотека Sizzle могла бы обрабатывать события вставки DOM. Мне любопытно узнать, будет ли код для работы с IE.

Следуя идее опроса, я прочитал, что некоторые свойства элемента недоступны, пока элемент не будет добавлен к документу (например, element.scrollTop), возможно, вы могли бы опросить это вместо того, чтобы выполнять весь обход DOM.

И последнее: в IE есть один подход, который стоит изучить - поиграть с событием onpropertychange. Я считаю, что добавление его в документ обязательно вызовет это событие по крайней мере для одного свойства.

Как я упоминал в комментариях, которые появляются непосредственно под моим вопросом, я не могу контролировать, когда объект добавляется в DOM, иначе я бы даже не задавал этот вопрос. Но спасибо за ответ!

Jason Bunting 22.10.2008 00:04

Что касается вашей первой идеи, это не такая узкая эвристика, как мне нужно - другой код может добавлять элементы DOM, и я должен был бы предположить, что при изменении длины это произошло из-за добавления элемента мой ... такое предположение противоречит нужному мне детерминизму.

Jason Bunting 22.10.2008 00:58

Понятно. Сегодня был комментарий Джона Ресига о библиотеке Peppy на Ajaxian, который, казалось, предполагал, что его библиотека Sizzle могла бы обрабатывать события вставки DOM. Мне любопытно посмотреть, будет ли код для работы с IE.

Leo 22.10.2008 01:56

Следуя идее опроса, я прочитал, что некоторые свойства элемента недоступны, пока элемент не будет добавлен к документу (например, element.scrollTop), возможно, вы могли бы опросить это вместо того, чтобы выполнять весь обход DOM.

Leo 22.10.2008 01:58

Да, что-то в этом роде могло бы быть неплохим, хотя я думал, что проект Sizzle должен был просто обеспечить более быструю реализацию селектора, не более того. В любом случае, Ресиг - сутенер.

Jason Bunting 22.10.2008 01:59

И последнее: в IE есть один подход, который стоит изучить - поиграть с событием onpropertychange. Я считаю, что добавление его в документ обязательно вызовет это событие по крайней мере для одного свойства.

Leo 22.10.2008 01:59

Интересная идея - я мог бы проверить и это, спасибо lhorie; когда у меня будет время, я изучу это еще немного, но пока, по крайней мере, у меня достаточно; хотя обход и опрос определенно отстой!

Jason Bunting 22.10.2008 02:00

Лори, не могли бы вы добавить эти идеи к своему фактическому ответу (просто отредактируйте его)? Я думаю, что они добавляют ценность, но поскольку они представляют собой комментарии (которые может легко удалить любой, у кого есть представитель), они не так потенциально полезны. Спасибо!

Jason Bunting 22.10.2008 02:01

В идеальном мире вы можете зацепить события мутации. Сомневаюсь, что они надежно работают даже в стандартных браузерах. Похоже, вы уже реализовали событие мутации, поэтому вы могли бы добавить его, чтобы использовать собственное событие мутации, а не опрос тайм-аута в браузерах, которые их поддерживают.

Я видел реализации изменения DOM, которые отслеживают изменения, сравнивая document.body.innerHTML с последним сохраненным .innerHTML, что не совсем элегантно (но работает). Поскольку в конечном итоге вы собираетесь проверить, добавлен ли еще конкретный узел, вам лучше просто проверять это каждое прерывание.

Я могу придумать улучшения используя .offsetParent, а не .parentNode, так как это, скорее всего, вырежет несколько родителей из вашего цикла (см. Комментарии). Или использование compareDocumentIndex() на всех, кроме IE, и тестирование свойств .sourceIndex для IE (должно быть -1, если узел не находится в DOM).

Это также может помочь: Реализация X browser compareDocumentIndex от Джона Ресига.

Оказывается, offsetParent не всегда работает очень хорошо ... он не всегда возвращается к самому документу, тогда как переход по дереву с помощью parentNode будет, если узел является частью дерева DOM; иначе он в конечном итоге возвращает null. Не все пути вверх по дереву с помощью offsetParent работают так.

Jason Bunting 23.10.2008 22:21

Кроме того, offsetParent, по-видимому, не является частью спецификации DOM: developer.mozilla.org/En/DOM/Element.offsetParent Более того, он «возвращает null, если для элемента style.display установлено значение 'none'», что немного испортило бы мне жизнь.

Jason Bunting 23.10.2008 22:25
Ответ принят как подходящий

ОБНОВЛЕНИЕ: для всех, кого это интересует, вот реализация, которую я наконец использовал:

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

Надеюсь, что это будет кому-то полезно.

ПРИМЕЧАНИЕ. Причина, по которой я остановился на этом решении, а не на Ответ Дэррила, заключалась в том, что метод getElementById работает только в том случае, если вы находитесь в одном документе; У меня есть несколько фреймов на странице, и страницы взаимодействуют между собой сложным образом - когда я пробовал это, проблема заключалась в том, что он не мог найти элемент, потому что он был частью другого документа, чем код, в котором он выполнялся. .

Я пробовал производительность, и прогулка по дереву dom выполняется быстрее, пока элемент отсутствует, jsperf.com/is-in-dom

hultqvist 18.05.2014 02:09

Вот еще одно решение проблемы, извлеченное из кода jQuery. Следующая функция может использоваться, чтобы проверить, подключен ли узел к DOM или он «отключен».

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

NodeType === 11 определяет DOCUMENT_FRAGMENT_NODE, который является узлом, не связанным с объектом документа.

Для получения дополнительной информации см. Следующую статью Джона Ресига: http://ejohn.org/blog/dom-documentfragments/

Боюсь, что работает не так, как ожидалось: var ol = document.createElement ('ol'); var li = ol.appendChild (document.createElement ('li')); ol.id = 'время воспроизведения'; isDisconnected (li); // возвращает false, я ожидаю, что вернет true document.getElementById ('playtime'); // ничего не возвращает

soniiic 10.06.2011 15:23

Самый простой ответ - использовать метод Node.contains, поддерживаемый Chrome, Firefox (Gecko), Internet Explorer, Opera и Safari. Вот пример:

var el = document.createElement("div");
console.info(document.body.contains(el)); // false
document.body.appendChild(el);
console.info(document.body.contains(el)); // true
document.body.removeChild(el);
console.info(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/

contains не является методом документирования - это свойство body. Итак, document.contains работать не будет, вам нужно получить к нему доступ через тело вот так: document.body.contains
Jason Bunting 06.12.2012 03:27

@JasonBunting, оба будут работать. contains - это метод всех объектов Node. И document, и document.body являются объектами Node, как и все элементы HTML.

Chris Calo 06.12.2012 05:54

Да, ну, в IE 9 document.contains == undefined, но document.body.contains! = Undefined. Итак, это не обязательно так, поэтому мой комментарий. :)

Jason Bunting 13.12.2012 20:40

взлом в опубликованной вами ссылке был очень полезен и работал как шарм в моем случае

gbaccetta 13.02.2017 17:11

Но проверяет ли это дерево? Если вы добавили еще один узел в качестве дочернего для первого узла с именем document.body.contains (childNode), найдет ли он дочерний узел, у которого есть родительский узел перед узлом документа?

Rob Evans 19.04.2017 16:57

@RobEvans, действительно будет.

Chris Calo 19.04.2017 16:59

Этот метод не может обнаружить динамически добавленные узлы в Edge (v42 +) и Firefox (v62 +). Chrome (v70) работает, и я не тестировал Safari. Я пытаюсь понять почему, но пока ничего не нашел. Конечно, в моем случае я на самом деле проверяю (parent) div.contains(child div), а не document.contains, поэтому не уверен, что это должно вести себя по-другому.

Mrchief 27.12.2018 05:44

Вместо того, чтобы идти по дереву DOM до элемента документа, просто используйте element.ownerDocument. см. здесь: https://developer.mozilla.org/en-US/docs/DOM/Node.ownerDocument и сделайте так:

element.ownerDocument.body.contains(element)

и ты в порядке.

var finalElement=null; 

Я создаю объекты dom в цикле, когда цикл находится в последнем цикле, а затем создается lastDomObject:

 finalElement=lastDomObject; 

оставить цикл:

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

Следующее действие может использовать любой из вновь созданных объектов dom. Сработало с первой попытки.

MutationObserver - это то, что вы должны использовать, чтобы определить, когда элемент был добавлен в DOM. MutationObservers теперь широко поддерживаются во всех современных браузерах (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.info("It's in the DOM!");
        observer.disconnect();
    }
});

observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});

$("body").append(myElement); // console.info: It's in the DOM!

Обработчик событий observer срабатывает всякий раз, когда какой-либо узел добавляется или удаляется из document. Затем внутри обработчика мы выполняем проверку contains, чтобы определить, находится ли сейчас myElement в document.

Вам не нужно перебирать каждую MutationRecord, хранящуюся в mutations, потому что вы можете выполнить проверку document.contains непосредственно после myElement.

Для повышения производительности замените document конкретным элементом, который будет содержать myElement в модели DOM.

Другие вопросы по теме