Как я могу получить уведомление о добавлении элемента на страницу?

142

Я хочу, чтобы функция по моему выбору запускалась, когда на страницу добавляется элемент DOM. Это в контексте расширения браузера, поэтому веб-страница работает независимо от меня, и я не могу изменить ее источник. Какие у меня здесь варианты?

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

Кевин Берк
источник
Нужно ли вам проверять конкретный элемент, добавленный на страницу другим вашим скриптом, или какой-либо добавленный элемент вне зависимости от источника?
Jose Faeti
1
знаете ли вы, какая функция добавляет элемент в чужой код, если это так, вы можете перезаписать его и добавить одну дополнительную строку, запускающую настраиваемое событие?
Jeffreydev

Ответы:

87

Предупреждение!

Этот ответ уже устарел. DOM Level 4 представил MutationObserver , обеспечивающий эффективную замену устаревших событий мутации. См. Этот ответ для получения лучшего решения, чем представленное здесь. Шутки в сторону. Не опрашивайте DOM каждые 100 миллисекунд; это приведет к потере мощности процессора, и ваши пользователи будут вас ненавидеть.

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

function checkDOMChange()
{
    // check for any new element being inserted here,
    // or a particular node being modified

    // call the function again after 100 milliseconds
    setTimeout( checkDOMChange, 100 );
}

После вызова этой функции она будет выполняться каждые 100 миллисекунд, что составляет 1/10 (одну десятую) секунды. Если вам не нужно наблюдение за элементами в реальном времени, этого должно быть достаточно.

Хосе Фаэти
источник
1
Джозе, как закончить время ожидания, когда условие действительно выполнено? т.е. вы нашли элемент, который, наконец, загрузился на вашу страницу через x секунд?
klewis
1
@blachawk вам необходимо присвоить значение, возвращаемое setTimeout, переменной, которую вы позже можете передать как параметр clearTimeout (), чтобы очистить его.
Хосе Фаэти
3
@blackhawk: Я только что увидел этот ответ и добавил +1; но вы должны знать, что на самом деле вам не нужно использовать здесь clearTimeout (), поскольку setTimeout () запускается только один раз! Достаточно одного 'if-else': не позволяйте выполнению передавать setTimeout () после того, как вы нашли вставленный элемент.
1111161171159459134
Полезное практическое руководство по использованию MutationObserver(): davidwalsh.name/mutationobserver-api
gfullam
4
Это грязно. Неужели нет «эквивалента» AS3 Event.ADDED_TO_STAGE?
горько-синий
45

Фактический ответ - «использовать наблюдателей за мутациями» (как указано в этом вопросе: определение того, был ли элемент HTML добавлен в DOM динамически ), однако поддержка (особенно в IE) ограничена ( http://caniuse.com/mutationobserver ) .

Итак, НАСТОЯЩИЙ ответ таков: «Используйте наблюдателей за мутациями .... в конце концов. Но пока воспользуйтесь ответом Хосе Фаэти» :)

Асфанд Кази
источник
19

Между прекращением поддержки событий мутации и появлением MutationObserverэффективного способа получать уведомления о добавлении определенного элемента в DOM было использование событий анимации CSS3. .

Чтобы процитировать сообщение в блоге:

Настройте последовательность ключевых кадров CSS, которая нацелена (с помощью выбранного вами селектора CSS) на любые элементы DOM, для которых вы хотите получить событие вставки узла DOM. Я использовал относительно мягкое и малоиспользуемое свойство css, clip. Я использовал контур-цвет, чтобы избежать путаницы с намеченными стилями страницы - код когда-то был нацелен на свойство clip, но его больше нельзя анимировать в IE с версии 11. Это сказал, любое свойство, которое можно анимировать, будет работать, выберите то, что вам нравится.

Затем я добавил прослушиватель анимации для всего документа, который я использую в качестве делегата для обработки вставок узлов. Событие анимации имеет свойство с именем animationName, которое сообщает вам, какая последовательность ключевых кадров запустила анимацию. Просто убедитесь, что свойство animationName совпадает с именем последовательности ключевых кадров, которое вы добавили для вставки узлов, и все готово.

анекдот
источник
Это решение с наивысшей производительностью, и в дублированном вопросе есть ответ, в котором упоминается библиотека для этого.
Дэн Даскалеску
1
Недооцененный ответ.
Gökhan Kurt
Осторожный. Если важна точность в миллисекундах, этот подход далеко не точен. Это jsbin показывает , что есть больше , чем 30мс разница между инлайн обратного вызова и использования animationstart, jsbin.com/netuquralu/1/edit .
Gajus
Я согласен с Куртом, это недооценено.
Zabbu
13

Вы можете использовать livequeryплагин для jQuery. Вы можете указать выражение селектора, например:

$("input[type=button].removeItemButton").livequery(function () {
    $("#statusBar").text('You may now remove items.');
});

Каждый раз, когда кнопка removeItemButton добавляется класса, в строке состояния появляется сообщение.

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

Повторный ответ

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

Однако, скорее всего, jQuery.on()будет более подходящим подход, например:

$("#myParentContainer").on('click', '.removeItemButton', function(){
          alert($(this).text() + ' has been removed');
});

Если у вас есть динамический контент, который должен реагировать, например, на щелчки, лучше всего привязать события к родительскому контейнеру, используя jQuery.on.

Нулевое отвлечение
источник
11

ETA 24 Apr 17 Я хотел немного упростить это с помощью некоторых async/await magic, так как это делает его более лаконичным:

Используя тот же самый обещанный-наблюдаемый:

const startObservable = (domNode) => {
  var targetNode = domNode;

  var observerConfig = {
    attributes: true,
    childList: true,
    characterData: true
  };

  return new Promise((resolve) => {
      var observer = new MutationObserver(function (mutations) {
         // For the sake of...observation...let's output the mutation to console to see how this all works
         mutations.forEach(function (mutation) {
             console.log(mutation.type);
         });
         resolve(mutations)
     });
     observer.observe(targetNode, observerConfig);
   })
} 

Ваша вызывающая функция может быть такой же простой, как:

const waitForMutation = async () => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {
      const results = await startObservable(someDomNode)
      return results
    } catch (err) { 
      console.error(err)
    }
}

Если вы хотите добавить тайм-аут, вы можете использовать простой Promise.raceшаблон, как показано здесь :

const waitForMutation = async (timeout = 5000 /*in ms*/) => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {

      const results = await Promise.race([
          startObservable(someDomNode),
          // this will throw after the timeout, skipping 
          // the return & going to the catch block
          new Promise((resolve, reject) => setTimeout(
             reject, 
             timeout, 
             new Error('timed out waiting for mutation')
          )
       ])
      return results
    } catch (err) { 
      console.error(err)
    }
}

Оригинал

Вы можете сделать это без библиотек, но вам придется использовать кое-что из ES6, поэтому помните о проблемах совместимости (например, если ваша аудитория - в основном амиши, луддиты или, что еще хуже, пользователи IE8)

Во-первых, мы будем использовать API MutationObserver для создания объекта-наблюдателя. Мы заключим этот объект в обещание, и resolve()когда будет запущен обратный вызов (h / t davidwalshblog), статья Дэвида Уолша о мутациях в блоге :

const startObservable = (domNode) => {
    var targetNode = domNode;

    var observerConfig = {
        attributes: true,
        childList: true,
        characterData: true
    };

    return new Promise((resolve) => {
        var observer = new MutationObserver(function (mutations) {
            // For the sake of...observation...let's output the mutation to console to see how this all works
            mutations.forEach(function (mutation) {
                console.log(mutation.type);
            });
            resolve(mutations)
        });
        observer.observe(targetNode, observerConfig);
    })
} 

Затем мы создадим файл generator function. Если вы еще не использовали их, значит, вы упускаете - но краткий синопсис: он работает как функция синхронизации, и когда он находит yield <Promise>выражение, он ожидает неблокирующим образом, чтобы обещание было выполнено. выполнено ( Генераторы делают больше, но это то, что нас здесь интересует ).

// we'll declare our DOM node here, too
let targ = document.querySelector('#domNodeToWatch')

function* getMutation() {
    console.log("Starting")
    var mutations = yield startObservable(targ)
    console.log("done")
}

Сложность генераторов заключается в том, что они не «возвращаются», как обычные функции. Итак, мы будем использовать вспомогательную функцию, чтобы иметь возможность использовать генератор как обычную функцию. (опять же, h / t в dwb )

function runGenerator(g) {
    var it = g(), ret;

    // asynchronously iterate over generator
    (function iterate(val){
        ret = it.next( val );

        if (!ret.done) {
            // poor man's "is it a promise?" test
            if ("then" in ret.value) {
                // wait on the promise
                ret.value.then( iterate );
            }
            // immediate value: just send right back in
            else {
                // avoid synchronous recursion
                setTimeout( function(){
                    iterate( ret.value );
                }, 0 );
            }
        }
    })();
}

Затем в любой момент до того, как может произойти ожидаемая мутация DOM, просто запустите runGenerator(getMutation).

Теперь вы можете интегрировать мутации DOM в поток управления синхронного стиля. Как насчет этого.

Брэндон
источник
8

Есть многообещающая библиотека javascript под названием Arrive, которая выглядит как отличный способ начать пользоваться преимуществами наблюдателей за мутациями, когда поддержка браузеров станет обычным явлением.

https://github.com/uzairfarooq/arrive/

Дэйв Кисс
источник
3

Посмотрите этот плагин, который именно это делает - jquery.initialize

Он работает точно так же, как и функция .each, разница в том, что он принимает введенный вами селектор и отслеживает новые элементы, добавленные в будущем, соответствующие этому селектору, и инициализирует их.

Инициализация выглядит так

$(".some-element").initialize( function(){
    $(this).css("color", "blue");
});

Но теперь, если .some-elementна странице появится новый селектор соответствия элемента , он будет мгновенно инициализирован.

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

Итак, если вы добавите новый элемент, например:

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

он будет немедленно инициализирован.

Плагин основан на MutationObserver

pie6k
источник
0

Решение на чистом javascript (без jQuery):

const SEARCH_DELAY = 100; // in ms

// it may run indefinitely. TODO: make it cancellable, using Promise's `reject`
function waitForElementToBeAdded(cssSelector) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      if (element = document.querySelector(cssSelector)) {
        clearInterval(interval);
        resolve(element);
      }
    }, SEARCH_DELAY);
  });
}

console.log(await waitForElementToBeAdded('#main'));
ведант
источник