Як чекати, поки елемент існує?


237

Я працюю над розширенням у Chrome, і мені цікаво: який найкращий спосіб дізнатися, коли елемент з’являється? Використовуючи звичайний JavaScript, з інтервалом, який перевіряє, поки елемент не існує, чи у jQuery є якийсь простий спосіб зробити це?


1
Схоже, кожен варіант сьогодні (включаючи коментарі) є застарілим або неповним. Вони не розглядають дивовижний вхід @ hughsk повністю аргументом сумісності. Тим часом я рекомендую просто використовувати оновлення Брендона на відповідь Райана для загальної простоти та меншого ризику накладних витрат.
Крего

4
MutationObserver> DOM Mutation Events> setTimeout.
маттсвен

2
Не з того, де я стою. setTimeoutсумісний, простий у виконанні, простий в обслуговуванні та має незначні витрати.
Крегокс

setTimeout+ jQueryна мою думку з двох причин є менш ідеальним: 1.) jQuery bloat 2.) Ви без потреби вручну запитуєте DOM щодо елементів, події легко перемагають цю швидкість, 3.) це завжди буде повільніше, ніж будь-який рідний реалізація. Якщо вам потрібно зробити що-небудь, ґрунтуючись на наявності елемента досить швидко, особливо якщо ваша мета - безперебійний досвід користувача, він неповноцінний.
маттсвен

3
Є 3 види людей: ті, хто вміє рахувати, і ті, хто не можуть. ; P
Крегокс

Відповіді:


149

DOMNodeInsertedвідміняється разом із іншими подіями мутації DOM через проблеми з продуктивністю - рекомендується використовувати MutationObserver для перегляду DOM. Він підтримується лише в новіших браузерах, тому вам слід повернутися назад, DOMNodeInsertedколи MutationObserverнедоступний.

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (!mutation.addedNodes) return

    for (var i = 0; i < mutation.addedNodes.length; i++) {
      // do things to your newly added nodes here
      var node = mutation.addedNodes[i]
    }
  })
})

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

// stop watching using:
observer.disconnect()

50
Я завжди знаходив MutationObserver api трохи складний, тому я створив бібліотеку, arrival.js , щоб забезпечити простіший api для прослуховування створення / видалення елементів.
Узаїр Фарук

15
Я рекомендую використовувати @UzairFarooq чудову бібліотеку github.com/uzairfarooq/arrive
Денніс

3
Дві речі, які слід зазначити: (1) Це було б краще зробити, if (mutation.addedNodes.length)оскільки if (mutation.addedNodes)все одно повернеться true, навіть якщо це порожній масив. (2) Ви не можете цього зробити, mutation.addedNodes.forEach()тому що addedNodes - це nodeList, і ви не можете перебирати через nodeList з forEach. Для вирішення цього питання дивіться toddmotto.com/ditch-the-array-foreach-call-nodelist-hack
thdoan

3
Чи можете ви навести приклад того, як можна було б це використовувати? Не знаю, куди потрібно поставити свій селектор або код jquery, який я хочу виконати, коли існує елемент DOM.
Супердопергеро

1
@Superdooperhero Я відповів простим прикладом. Перевір це. stackoverflow.com/a/57395241/6542186
Silversurfer

113

У мене була ця сама проблема, тому я пішов вперед і написав плагін для цього.

$(selector).waitUntilExists(function);

Код:

;(function ($, window) {

var intervals = {};
var removeListener = function(selector) {

    if (intervals[selector]) {

        window.clearInterval(intervals[selector]);
        intervals[selector] = null;
    }
};
var found = 'waitUntilExists.found';

/**
 * @function
 * @property {object} jQuery plugin which runs handler function once specified
 *           element is inserted into the DOM
 * @param {function|string} handler 
 *            A function to execute at the time when the element is inserted or 
 *            string "remove" to remove the listener from the given selector
 * @param {bool} shouldRunHandlerOnce 
 *            Optional: if true, handler is unbound after its first invocation
 * @example jQuery(selector).waitUntilExists(function);
 */

$.fn.waitUntilExists = function(handler, shouldRunHandlerOnce, isChild) {

    var selector = this.selector;
    var $this = $(selector);
    var $elements = $this.not(function() { return $(this).data(found); });

    if (handler === 'remove') {

        // Hijack and remove interval immediately if the code requests
        removeListener(selector);
    }
    else {

        // Run the handler on all found elements and mark as found
        $elements.each(handler).data(found, true);

        if (shouldRunHandlerOnce && $this.length) {

            // Element was found, implying the handler already ran for all 
            // matched elements
            removeListener(selector);
        }
        else if (!isChild) {

            // If this is a recurring search or if the target has not yet been 
            // found, create an interval to continue searching for the target
            intervals[selector] = window.setInterval(function () {

                $this.waitUntilExists(handler, shouldRunHandlerOnce, true);
            }, 500);
        }
    }

    return $this;
};

}(jQuery, window));

5
Дякую за плагін Я трохи роздвоївся та покращив його. Ви можете взяти все, що завгодно, з мого оновлення. У мене є ще кілька запланованих вдосконалень: оновлений плагін
Брендон Белвін

8
було б добре без jquery dep too ...;)
knutole

4
можливо, ви повинні згадати, як це працює: він працює, запитуючи кожні 500 мс, чи існує елемент (використовуючи a window.setInterval). Я не знаю, чи MutationObserverпрацює відповідь, опитуючи також ...
спорт

2
Він не працює належним чином, якщо елемент вже є на сторінці. Ось правильна версія цієї функції: gist.github.com/PizzaBrandon/5709010
Roland Soós

2
Чи можете ви пояснити, у чому полягає користь ;на початку функції ( ;(function ($, window) {)?
середина

76

Ось основна функція JavaScript для очікування відображення елемента.

Параметри:

  1. selector: Ця функція шукає елемент $ {selector}
  2. time: Ця функція перевіряє, чи існує цей елемент кожні $ {time} мілісекунди.

    function waitForElementToDisplay(selector, time) {
            if(document.querySelector(selector)!=null) {
                alert("The element is displayed, you can put your code instead of this alert.")
                return;
            }
            else {
                setTimeout(function() {
                    waitForElementToDisplay(selector, time);
                }, time);
            }
        }

Як приклад, налаштування selector="#div1"і time=5000буде шукати тег HTML, чиї id="div1"кожні 5000 мілісекунд.


Приємно! Чи можете ви написати це, щоб будь-який вибір був прийнятий?
матцвен

Я сумніваюся, що я можу це зробити. Але, будь ласка, подивіться на цю публікацію, щоб отримати getElementByXpath: stackoverflow.com/questions/10596417/…
Етьєн Тоннельє


1
Чи можете ви написати, щоб замість цього використовувався спостерігач мутацій?
SuperUberDuper

чи можете ви переписати цю, щоб використати обіцянку?
SuperUberDuper

25

Ви можете слухати DOMNodeInsertedабо DOMSubtreeModifiedподії, які запускаються кожного разу, коли до DOM додається новий елемент.

Є також плагін LiveQuery jQuery, який би визначав, коли створюється новий елемент:

$("#future_element").livequery(function(){
    //element created
});

1
Дуже приємний плагін! Чи є така функція в jquery безпосередньо? Мені цікаво, що для цього немає жодної функції. І якщо це ТО плагін, будь ласка, проголосуйте за цю відповідь;) Для мене він працює чудово. Велике спасибі.
Самуїл

1
Примітка. IE 9 реалізує DOMNodeInserted, але має основну помилку, де вона не запуститься, коли ви додасте елемент за той час, який є більшою частиною часу, коли ви хочете його використовувати. Деталі розміщені на: help.dottoro.com/ljmcxjla.php
mikemaccana

23

Я використовував такий підхід, щоб дочекатися появи елемента, щоб я міг виконувати інші функції після цього.

Скажімо, doTheRestOfTheStuff(parameters)функцію слід викликати лише після появи елемента з ідентифікатором the_Element_IDабо завершення завантаження, ми можемо використовувати,

var existCondition = setInterval(function() {
 if ($('#the_Element_ID').length) {
    console.log("Exists!");
    clearInterval(existCondition);
    doTheRestOfTheStuff(parameters);
 }
}, 100); // check every 100ms

21

Ви можете зробити

$('#yourelement').ready(function() {

});

Зверніть увагу, що це буде працювати лише в тому випадку, якщо елемент присутній у DOM при запиті від сервера. Якщо елемент динамічно додається через JavaScript, він не працюватиме, і вам може знадобитися переглянути інші відповіді.


7
У .ready()функції працює для більшості нічого (якщо не що - небудь), а не просто document. Він просто не працюватиме з динамічно створеними елементами, навіть на .live().
Річард Ніл Ілаган

7
@Bery, як зазначав Річард, це працює лише для елементів, які вже є в HTML, коли він вперше запитується від сервера. Якщо Javascript використовується для динамічного додавання елемента до DOM, він не працює.
Чандраншу

6
@Sam, чи можете ви уточнити, як приєднати його до посилання елемента в пам'яті?
Вікас Сінгал

3
Ця відповідь невірна. Те, що ви насправді перевіряєте тут, є звичайним $(document).ready(), а не елементом, на який ви думаєте, що він також буде застосовуватися. Ось так працює цей спеціальний слухач. Приклад
Шиккедіел

1
Це використання не рекомендується відповідно до api.jquery.com/ready
splintor

14

Я думаю, що все ще немає жодної відповіді тут, з легким і читабельним робочим прикладом. Використовуйте MutationObserver interface для виявлення змін у DOM, наприклад:

var observer = new MutationObserver(function(mutations) {
    if ($("p").length) {
        console.log("Exist, lets do something");
        observer.disconnect(); 
        //We can disconnect observer once the element exist if we dont want observe more changes in the DOM
    }
});

// Start observing
observer.observe(document.body, { //document.body is node target to observe
    childList: true, //This is a must have for the observer with subtree
    subtree: true //Set to true if changes must also be observed in descendants.
});
            
$(document).ready(function() {
    $("button").on("click", function() {
        $("p").remove();
        setTimeout(function() {
            $("#newContent").append("<p>New element</p>");
        }, 2000);
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button>New content</button>
<div id="newContent"></div>

Примітка: іспанські документи Mozilla про MutationObserverбільш детальні, якщо ви хочете отримати більше інформації.


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

12

Просто додайте потрібний селектор. Після того як елемент знайдений, ви можете отримати доступ до функції зворотного дзвінка.

const waitUntilElementExists = (selector, callback) => {
const el = document.querySelector(selector);

if (el){
    return callback(el);
}

setTimeout(() => waitUntilElementExists(selector, callback), 500);
}

waitUntilElementExists('.wait-for-me', (el) => console.log(el));

2
PossessWithin згоден, це дуже чисте рішення і працює для мене.
jstafford

3
Ця відповідь працює як на IE8-10, так і на сучасних браузерах. Основна проблема полягає в тому, що він буде продовжувати працювати, якщо елемент не існує - тому найкраще, коли ти впевнений, що елемент буде там. В іншому випадку ви можете додати лічильник.
Для імені

1
Для мене прекрасно працював
Джеймс Стюарт

1
Працював як шарм !!
Аман

1
Вони були схожі, не тотожні. Крім того, багато людей роблять те саме. Нарешті, я сам закодував це рішення. Це неправильне обґрунтування, однак, якби навіть це було дійсно так, я би вдячний за коментар, який дає мені знати. Відповідь вирішує питання ОП і не має очевидних мотивів, які слід уникати.
Дієго Фортес

11

Для простого підходу з використанням jQuery я виявив, що це працює добре:

  // Wait for element to exist.
  function elementLoaded(el, cb) {
    if ($(el).length) {
      // Element is now loaded.
      cb($(el));
    } else {
      // Repeat every 500ms.
      setTimeout(function() {
        elementLoaded(el, cb)
      }, 500);
    }
  };

  elementLoaded('.element-selector', function(el) {
    // Element is ready to use.
    el.click(function() {
      alert("You just clicked a dynamically inserted element");
    });
  });

Тут ми просто перевіряємо кожні 500 мс, щоб побачити, чи завантажений елемент, коли він є, чи можемо ми його використовувати.

Це особливо корисно для додавання обробників кліків до елементів, що були динамічно додані в документ.


8

Як щодо бібліотеки insertionQuery ?

inserttionQuery використовує зворотні виклики CSS Animation, приєднані до селекторів, визначених для запуску зворотного виклику при створенні елемента. Цей метод дозволяє запускати виклики, коли елемент створюється, а не лише в перший раз.

Від github:

Недоступний спосіб події, щоб зловити вузли, що з’являються. І він використовує селектори.

Це не лише для ширшої підтримки браузера. Він може бути кращим, ніж DOMMutationObserver для певних речей.

Чому?

  • Оскільки події DOM сповільнюють браузер, а введенняQuery не робить
  • Оскільки у DOM Mutation Observer є менша підтримка браузера, ніж вставкаQuery
  • Тому що за допомогою insertionQuery ви можете фільтрувати зміни DOM за допомогою селекторів без накладних витрат!

Широка підтримка!

IE10 + і в основному все інше (включаючи мобільний)


7

Ось функція, яка виконує функції тонкої обгортки навколо MutationObserver. Єдина вимога - браузер підтримує MutationObserver; немає залежності від JQuery. Запустіть фрагмент нижче, щоб переглянути робочий приклад.

function waitForMutation(parentNode, isMatchFunc, handlerFunc, observeSubtree, disconnectAfterMatch) {
  var defaultIfUndefined = function(val, defaultVal) {
    return (typeof val === "undefined") ? defaultVal : val;
  };

  observeSubtree = defaultIfUndefined(observeSubtree, false);
  disconnectAfterMatch = defaultIfUndefined(disconnectAfterMatch, false);

  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.addedNodes) {
        for (var i = 0; i < mutation.addedNodes.length; i++) {
          var node = mutation.addedNodes[i];
          if (isMatchFunc(node)) {
            handlerFunc(node);
            if (disconnectAfterMatch) observer.disconnect();
          };
        }
      }
    });
  });

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

// Example
waitForMutation(
  // parentNode: Root node to observe. If the mutation you're looking for
  // might not occur directly below parentNode, pass 'true' to the
  // observeSubtree parameter.
  document.getElementById("outerContent"),
  // isMatchFunc: Function to identify a match. If it returns true,
  // handlerFunc will run.
  // MutationObserver only fires once per mutation, not once for every node
  // inside the mutation. If the element we're looking for is a child of
  // the newly-added element, we need to use something like
  // node.querySelector() to find it.
  function(node) {
    return node.querySelector(".foo") !== null;
  },
  // handlerFunc: Handler.
  function(node) {
    var elem = document.createElement("div");
    elem.appendChild(document.createTextNode("Added node (" + node.innerText + ")"));
    document.getElementById("log").appendChild(elem);
  },
  // observeSubtree
  true,
  // disconnectAfterMatch: If this is true the hanlerFunc will only run on
  // the first time that isMatchFunc returns true. If it's false, the handler
  // will continue to fire on matches.
  false);

// Set up UI. Using JQuery here for convenience.

$outerContent = $("#outerContent");
$innerContent = $("#innerContent");

$("#addOuter").on("click", function() {
  var newNode = $("<div><span class='foo'>Outer</span></div>");
  $outerContent.append(newNode);
});
$("#addInner").on("click", function() {
  var newNode = $("<div><span class='foo'>Inner</span></div>");
  $innerContent.append(newNode);
});
.content {
  padding: 1em;
  border: solid 1px black;
  overflow-y: auto;
}
#innerContent {
  height: 100px;
}
#outerContent {
  height: 200px;
}
#log {
  font-family: Courier;
  font-size: 10pt;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>Create some mutations</h2>
<div id="main">
  <button id="addOuter">Add outer node</button>
  <button id="addInner">Add inner node</button>
  <div class="content" id="outerContent">
    <div class="content" id="innerContent"></div>
  </div>
</div>
<h2>Log</h2>
<div id="log"></div>


6

Ось рішення, що повертає обіцянку у ванільному Javascript (без брудних зворотних викликів). За замовчуванням він перевіряє кожні 200 мс.

function waitFor(selector) {
    return new Promise(function (res, rej) {
        waitForElementToDisplay(selector, 200);
        function waitForElementToDisplay(selector, time) {
            if (document.querySelector(selector) != null) {
                res(document.querySelector(selector));
            }
            else {
                setTimeout(function () {
                    waitForElementToDisplay(selector, time);
                }, time);
            }
        }
    });
}

5

Ось чиста функція Javascript, яка дозволяє чекати чого завгодно. Встановіть інтервал довше, щоб зайняти менше ресурсу процесора.

/**
 * @brief Wait for something to be ready before triggering a timeout
 * @param {callback} isready Function which returns true when the thing we're waiting for has happened
 * @param {callback} success Function to call when the thing is ready
 * @param {callback} error Function to call if we time out before the event becomes ready
 * @param {int} count Number of times to retry the timeout (default 300 or 6s)
 * @param {int} interval Number of milliseconds to wait between attempts (default 20ms)
 */
function waitUntil(isready, success, error, count, interval){
    if (count === undefined) {
        count = 300;
    }
    if (interval === undefined) {
        interval = 20;
    }
    if (isready()) {
        success();
        return;
    }
    // The call back isn't ready. We need to wait for it
    setTimeout(function(){
        if (!count) {
            // We have run out of retries
            if (error !== undefined) {
                error();
            }
        } else {
            // Try again
            waitUntil(isready, success, error, count -1, interval);
        }
    }, interval);
}

Щоб викликати це, наприклад, у jQuery, використовуйте щось на кшталт:

waitUntil(function(){
    return $('#myelement').length > 0;
}, function(){
    alert("myelement now exists");
}, function(){
    alert("I'm bored. I give up.");
});

3

Рішення, яке повертає Promiseта дозволяє використовувати тайм-аут (сумісний IE 11+).

Для одного елемента (тип Element):

"use strict";

function waitUntilElementLoaded(selector) {
    var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

    var start = performance.now();
    var now = 0;

    return new Promise(function (resolve, reject) {
        var interval = setInterval(function () {
            var element = document.querySelector(selector);

            if (element instanceof Element) {
                clearInterval(interval);

                resolve();
            }

            now = performance.now();

            if (now - start >= timeout) {
                reject("Could not find the element " + selector + " within " + timeout + " ms");
            }
        }, 100);
    });
}

Для декількох елементів (введіть NodeList):

"use strict";

function waitUntilElementsLoaded(selector) {
    var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

    var start = performance.now();
    var now = 0;

    return new Promise(function (resolve, reject) {
        var interval = setInterval(function () {
            var elements = document.querySelectorAll(selector);

            if (elements instanceof NodeList) {
                clearInterval(interval);

                resolve(elements);
            }

            now = performance.now();

            if (now - start >= timeout) {
                reject("Could not find elements " + selector + " within " + timeout + " ms");
            }
        }, 100);
    });
}

Приклади:

waitUntilElementLoaded('#message', 800).then(function(element) {
    // element found and available

    element.innerHTML = '...';
}).catch(function() {
    // element not found within 800 milliseconds
});

waitUntilElementsLoaded('.message', 10000).then(function(elements) {
    for(const element of elements) {
        // ....
    }
}).catch(function(error) {
    // elements not found withing 10 seconds
});

Працює як для списку елементів, так і для одного елемента.


1
Моє улюблене рішення! Навіщо перевіряти element instanceof HTMLElement? Чи може це коли-небудь бути, крім nullабо HTMLElement?
Лірой

1
Ви піднімаєте цікавий момент. Я мав би зробити його ширшим, використовуючи Elementнатомість (фіксований). Я просто роблю перевірку, бо хочу бути впевненим, що змінна elementмає властивість, innerHTMLяк зазначено в документації Element MDN . Сміливо зніміть його, якщо вам це не байдуже!
Анвар

2

Чіткіший приклад використання MutationObserver:

new MutationObserver( mutation => {
    if (!mutation.addedNodes) return
    mutation.addedNodes.forEach( node => {
        // do stuff with node
    })
})

2

Це просте рішення для тих, хто звик до обіцянок і не хоче використовувати жодних сторонніх ліб або таймерів.

Я деякий час використовую його у своїх проектах

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

Щоб використовувати його:

waitForElm('.some-class').then(elm => console.log(elm.textContent));

або з асинхронізацією / очікуванням

const elm = await waitForElm('.some-classs')

Це акуратно! Прикольна частина цього полягає в тому, що ви можете використовувати його з async/ awaitтеж. Можливо, ви також зможете вичавити з нього більше продуктивності, зробивши цеmutations.addedNodes.find(node => node.matchesSelector("..."))
mattsven

@mattsven Добрий момент! Перевірка лише вузлів у мутаціях є більш ефективною, ніж робити document.querySelector.
Йонг Ван

Виправте орфографічну помилку, watiForElm чекатиForElm
dalvir

1

Якщо ви хочете, щоб він перестав шукати деякий час (таймаут), наступний jQuery запрацює. Час очікується через 10 сек. Мені потрібно було використовувати цей код, а не чистий JS, тому що мені потрібно було вибрати вхід через ім'я та виникли проблеми з реалізацією деяких інших рішень.

 // Wait for element to exist.

    function imageLoaded(el, cb,time) {

        if ($(el).length) {
            // Element is now loaded.

            cb($(el));

            var imageInput =  $('input[name=product\\[image_location\\]]');
            console.log(imageInput);

        } else if(time < 10000) {
            // Repeat every 500ms.
            setTimeout(function() {
               time = time+500;

                imageLoaded(el, cb, time)
            }, 500);
        }
    };

    var time = 500;

    imageLoaded('input[name=product\\[image_location\\]]', function(el) {

     //do stuff here 

     },time);

0

Зазвичай я використовую цей фрагмент для Менеджера тегів:

<script>
(function exists() {
  if (!document.querySelector('<selector>')) {
    return setTimeout(exists);
  }
  // code when element exists
})();  
</script>

0

якщо у вас зміни дому асинхронізації, ця функція перевіряє (з обмеженням часу в секундах) елементи DOM, вона не буде важкою для DOM та її Обіцянки :)

function getElement(selector, i = 5) {
  return new Promise(async (resolve, reject) => {
    if(i <= 0) return reject(`${selector} not found`);
    const elements = document.querySelectorAll(selector);
    if(elements.length) return resolve(elements);
    return setTimeout(async () => await getElement(selector, i-1), 1000);
  })
}

// Now call it with your selector

try {
  element = await getElement('.woohoo');
} catch(e) { // catch the e }

//OR

getElement('.woohoo', 5)
.then(element => { // do somthing with the elements })
.catch(e => { // catch the error });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.