Як я можу отримувати сповіщення, коли елемент доданий на сторінку?


139

Я хочу, щоб функція мого вибору була запущена, коли на сторінку додано елемент DOM. Це в контексті розширення для браузера, тому веб-сторінка працює незалежно від мене, і я не можу змінити її джерело. Які тут мої варіанти?

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


Чи потрібно вам перевірити для певного елемента інший ваш скрипт, поміщений на сторінку, чи на будь-який елемент, який додається незалежно від джерела?
Хосе Фаети

1
чи знаєте ви, яка функція додає елемент у чужий код, якщо так, ви можете його перезаписати та додати ще один додатковий рядок, що запускає власну подію?
jeffreydev

Відповіді:


86

Увага!

Ця відповідь застаріла. 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
Джозе, як ви закінчуєте час роботи, коли умова фактично виконана? тобто ви знайшли елемент, який остаточно завантажив х секунд пізніше на вашу сторінку?
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?
Bitterblue

45

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

Тож власне АКТУАЛЬНА відповідь - "Використовуйте спостерігачів за мутацією .... врешті-решт. Але продовжте відповідь Хосе Фаті" :)


19

Між припиненням подій мутації та виникненням MutationObserver, ефективним способом сповіщення, коли до DOM був доданий певний елемент, було використання подій анімації CSS3 .

Щоб процитувати публікацію в блозі:

Встановіть послідовність ключових кадрів CSS, яка орієнтується (за вибором селектора CSS) на будь-які елементи DOM, для яких потрібно отримати подію вставлення вузла DOM. Я використовував відносно доброякісне і мало використовуване css властивість, clip Я використовував контурний колір, щоб уникнути возитися із призначеними стилями сторінки - код колись орієнтувався на властивість кліпу, але він більше не анімаційний в IE, починаючи з версії 11. Це сказати, будь-яка властивість, яку можна анімувати, працюватиме, вибирайте, який вам подобається.

Далі я додав слухач анімації для всього документа, який використовую як делегат для обробки вставок вузлів. Подія анімації має властивість під назвою animationName, яка повідомляє вам, яка послідовність ключових кадрів розпочала анімацію. Просто переконайтеся, що властивість animationName збігається з іменем послідовності ключових кадрів, яке ви додали для вставки вузла, і вам добре пройти.


Це рішення з найвищою ефективністю, і там є відповідь у двох примірниках, де згадується бібліотека.
Дан Даскалеску

1
Занижена відповідь.
Гехан Курт

Дбайливий. Якщо важлива точність мілісекунд, такий підхід далеко не точний. Цей jsbin демонструє, що між вбудованим зворотним дзвінком та використанням animationstart, jsbin.com/netuquralu/1/edit , існує більше 30 мс різниці .
Гаджус

Я погоджуюся з куром, це недооцінено.
Заббу

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 квітня 17 Я хотів трохи спростити це з деякими async/await магіями, оскільки це робить набагато більш лаконічним:

Використовуючи ті ж самі обіцяні-спостерігаючі:

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()коли зворотний виклик буде знято (ч / т davidwalshblog) стаття в блозі про David Walsh про мутації :

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")
}

Хитра частина щодо генераторів - це те, що вони не «повертаються» як нормальна функція. Отже, ми будемо використовувати допоміжну функцію, щоб мати можливість використовувати генератор як звичайну функцію. (знову ж, год / т до відходу )

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


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'));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.