Согласно спецификации, только элементы BODY и FRAMESET предоставляют событие «onload» для присоединения, но я хотел бы знать, когда динамически созданный элемент DOM был добавлен в DOM в JavaScript.
В настоящее время я использую сверхнаивные эвристики, которые работают, а именно:
Мне нужно либо подтверждение того, что то, что я делаю, достаточно (опять же, это работает как в IE7, так и в FF3), либо лучшее решение, о котором по какой-то причине я совершенно не замечал; возможно, другие свойства, которые я должен проверить, и т. д.
Обновлено: мне нужен независимый от браузера способ сделать это, к сожалению, я не живу в мире с одним браузером; Тем не менее, информация, относящаяся к конкретному браузеру, приветствуется, но обратите внимание, в каком браузере вы знаете, что он делает работает. Спасибо!
@Sergey: Чем больше абстрагируешь, тем больше вероятность, что одна рука не знает, что делает другая.
Точно - я создаю «виджет», который внутренне должен будет знать, когда он был добавлен в DOM, но поскольку я не обязательно являюсь потребителем, я не буду знать когда, потому что это определит клиентский код.
Однако событие onload IMG не является частью спецификации W3C. Кроме того, мне нужна эта функциональность не только для изображений, поэтому она не принесла бы мне много пользы и, кроме того, была бы немного более хрупкой.
В браузере есть метод определения того, находится ли один узел внутри другого: Node.contains. Добавил ответ ниже: stackoverflow.com/a/13725984/101869



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вам нужно событие DOMNodeInserted (или DOMNodeInsertedIntoDocument).
Обновлено: вполне возможно, что эти события не поддерживаются IE, но я не могу это проверить прямо сейчас.
Спасибо - правильно ли я предполагаю, что это идиома, специфичная для Mozilla / Firefox / Gecko?
Нет, у тебя нет на это права. Это стандартная идиома DOM-Events.
Понятно. Тем не менее, я не думаю, что IE (рыжий пасынок браузеров) его поддерживает.
Можете ли вы сделать document.getElementById('newElementId'); и посмотреть, вернет ли это значение. Если нет, как вы говорите, подождите 100 мс и попробуйте еще раз?
Что делать, если у него нет идентификатора? Тогда не получится, а может и так. В противном случае хороший ответ ... может быть, я посмотрю, сработает ли это для некоторых из моих потребностей, и воспользуюсь другим методом, если у меня нет идентификатора.
Почему бы просто не добавить идентификатор? Вы можете добавить уникальный идентификатор, используя какую-то случайную строку, а затем просто искать ее. Я знаю, что вам не нравится jQuery, но с jQuery вы можете легко найти его на основе, возможно, класса, родителя или ребенка.
Правда, это может быть хорошим способом сделать это - мне придется немного подумать об этом ... спасибо. LOL @ мне не нравится jQuery. Мне это нравится, мне просто не нравится, когда люди думают, что это ответ на все вопросы о JavaScript. :)
Так что мне это нравится и идеи Боргара тоже. Я мог бы проверить, есть ли у рассматриваемого элемента свойство ID; если это так, используйте это с getElementById, иначе создайте случайный, надеюсь, уникальный идентификатор и назначьте его; когда getElementById найдет его, удалите изначально несуществующий идентификатор и перейдите оттуда.
Мне нравится эта идея. Это лаконично. Это практически уверенность в том, что document.getElementById () будет работать, если что-то делает. :-)
Верно то, что простота, согласованность и надежность во всех браузерах сделали бы это лучшим решением; Я действительно не хочу писать код для браузера, поэтому изменю утвержденный ответ.
Ни одно из событий DOMNodeInserted или DOMNodeInsertedIntoDocument не поддерживается в IE. Другой отсутствующей в IE [only] функции DOM является функция compareDocumentPosition, которая предназначена для выполнения того, что предлагает ее название.
Что касается предлагаемого решения, я думаю, что нет лучшего (если вы не сделаете какие-то грязные трюки, такие как перезапись реализаций собственных членов DOM)
Также исправляем заголовок вопроса: динамически созданный элемент является частью DOM с момента его создания, он может быть добавлен к фрагменту документа или к другому элементу, но не может быть добавлен к документу.
Я также думаю, что описанная вами проблема - это не та проблема, которая у вас есть, это скорее одно из возможных решений вашей проблемы. Вы, наверное, можете подробно объяснить вариант использования?
Спасибо за ответ - проблема, которую я описал является как проблема, с которой я столкнулся, но спасибо за попытку прочитать мои мысли в любом случае. :) Я абстрагировался от деталей моей проблемы до такой степени, что ответа на этот вопрос будет достаточно для решения моей более конкретной проблемы.
Вы можете запросить 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, иначе я бы даже не задавал этот вопрос. Но спасибо за ответ!
Что касается вашей первой идеи, это не такая узкая эвристика, как мне нужно - другой код может добавлять элементы DOM, и я должен был бы предположить, что при изменении длины это произошло из-за добавления элемента мой ... такое предположение противоречит нужному мне детерминизму.
Понятно. Сегодня был комментарий Джона Ресига о библиотеке Peppy на Ajaxian, который, казалось, предполагал, что его библиотека Sizzle могла бы обрабатывать события вставки DOM. Мне любопытно посмотреть, будет ли код для работы с IE.
Следуя идее опроса, я прочитал, что некоторые свойства элемента недоступны, пока элемент не будет добавлен к документу (например, element.scrollTop), возможно, вы могли бы опросить это вместо того, чтобы выполнять весь обход DOM.
Да, что-то в этом роде могло бы быть неплохим, хотя я думал, что проект Sizzle должен был просто обеспечить более быструю реализацию селектора, не более того. В любом случае, Ресиг - сутенер.
И последнее: в IE есть один подход, который стоит изучить - поиграть с событием onpropertychange. Я считаю, что добавление его в документ обязательно вызовет это событие по крайней мере для одного свойства.
Интересная идея - я мог бы проверить и это, спасибо lhorie; когда у меня будет время, я изучу это еще немного, но пока, по крайней мере, у меня достаточно; хотя обход и опрос определенно отстой!
Лори, не могли бы вы добавить эти идеи к своему фактическому ответу (просто отредактируйте его)? Я думаю, что они добавляют ценность, но поскольку они представляют собой комментарии (которые может легко удалить любой, у кого есть представитель), они не так потенциально полезны. Спасибо!
В идеальном мире вы можете зацепить события мутации. Сомневаюсь, что они надежно работают даже в стандартных браузерах. Похоже, вы уже реализовали событие мутации, поэтому вы могли бы добавить его, чтобы использовать собственное событие мутации, а не опрос тайм-аута в браузерах, которые их поддерживают.
Я видел реализации изменения DOM, которые отслеживают изменения, сравнивая document.body.innerHTML с последним сохраненным .innerHTML, что не совсем элегантно (но работает). Поскольку в конечном итоге вы собираетесь проверить, добавлен ли еще конкретный узел, вам лучше просто проверять это каждое прерывание.
Я могу придумать улучшения используя (см. Комментарии). Или использование .offsetParent, а не .parentNode, так как это, скорее всего, вырежет несколько родителей из вашего циклаcompareDocumentIndex() на всех, кроме IE, и тестирование свойств .sourceIndex для IE (должно быть -1, если узел не находится в DOM).
Это также может помочь: Реализация X browser compareDocumentIndex от Джона Ресига.
Оказывается, offsetParent не всегда работает очень хорошо ... он не всегда возвращается к самому документу, тогда как переход по дереву с помощью parentNode будет, если узел является частью дерева DOM; иначе он в конечном итоге возвращает null. Не все пути вверх по дереву с помощью offsetParent работают так.
Кроме того, offsetParent, по-видимому, не является частью спецификации DOM: developer.mozilla.org/En/DOM/Element.offsetParent Более того, он «возвращает null, если для элемента style.display установлено значение 'none'», что немного испортило бы мне жизнь.
ОБНОВЛЕНИЕ: для всех, кого это интересует, вот реализация, которую я наконец использовал:
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
Вот еще одно решение проблемы, извлеченное из кода 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'); // ничего не возвращает
Самый простой ответ - использовать метод 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@JasonBunting, оба будут работать. contains - это метод всех объектов Node. И document, и document.body являются объектами Node, как и все элементы HTML.
Да, ну, в IE 9 document.contains == undefined, но document.body.contains! = Undefined. Итак, это не обязательно так, поэтому мой комментарий. :)
взлом в опубликованной вами ссылке был очень полезен и работал как шарм в моем случае
Но проверяет ли это дерево? Если вы добавили еще один узел в качестве дочернего для первого узла с именем document.body.contains (childNode), найдет ли он дочерний узел, у которого есть родительский узел перед узлом документа?
@RobEvans, действительно будет.
Этот метод не может обнаружить динамически добавленные узлы в Edge (v42 +) и Firefox (v62 +). Chrome (v70) работает, и я не тестировал Safari. Я пытаюсь понять почему, но пока ничего не нашел. Конечно, в моем случае я на самом деле проверяю (parent) div.contains(child div), а не document.contains, поэтому не уверен, что это должно вести себя по-другому.
Вместо того, чтобы идти по дереву 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.
Как вы можете не знать, когда в ваш документ добавляется какой-то контент? Вы не автор страницы?