Làm thế nào tôi có thể được thông báo khi một yếu tố được thêm vào trang?


139

Tôi muốn một chức năng tôi chọn chạy khi một phần tử DOM được thêm vào trang. Đây là trong bối cảnh của một phần mở rộng trình duyệt, vì vậy trang web chạy độc lập với tôi và tôi không thể sửa đổi nguồn của nó. lựa chọn của tôi là gì đây?

Tôi đoán rằng, về mặt lý thuyết, tôi chỉ có thể sử dụng setInterval()để liên tục tìm kiếm sự hiện diện của phần tử và thực hiện hành động của mình nếu phần tử ở đó, nhưng tôi cần một cách tiếp cận tốt hơn.


Bạn có phải kiểm tra một phần tử cụ thể mà tập lệnh khác của bạn đưa vào trang hoặc cho bất kỳ phần tử nào được thêm vào bất kể nguồn nào không?
Jose Faeti

1
Bạn có biết hàm nào thêm phần tử vào mã của người khác không, nếu vậy bạn có thể ghi đè lên nó và thêm một dòng bổ sung kích hoạt một sự kiện tùy chỉnh không?
jeffreydev

Câu trả lời:


86

Cảnh báo!

Câu trả lời này đã lỗi thời. DOM Cấp 4 đã giới thiệu MutingObserver , cung cấp một sự thay thế hiệu quả cho các sự kiện đột biến không dùng nữa. Xem câu trả lời này cho một giải pháp tốt hơn so với giải pháp được trình bày ở đây. Nghiêm túc. Đừng thăm dò DOM cứ sau 100 mili giây; nó sẽ lãng phí năng lượng CPU và người dùng của bạn sẽ ghét bạn.

các sự kiện đột biến không được chấp nhận vào năm 2012 và bạn không kiểm soát được các yếu tố được chèn bởi vì chúng được thêm bởi mã của người khác, nên lựa chọn duy nhất của bạn là liên tục kiểm tra chúng.

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

Khi chức năng này được gọi, nó sẽ chạy cứ sau 100 mili giây, tức là 1/10 (một phần mười) của một giây. Trừ khi bạn cần quan sát yếu tố thời gian thực, nó sẽ là đủ.


1
jose, làm thế nào để bạn kết thúc giải quyết của bạn khi điều kiện thực sự được đáp ứng? tức là bạn đã tìm thấy phần tử cuối cùng đã tải x giây sau lên trang của bạn?
klewis

1
@blachawk bạn cần gán giá trị trả về setTimeout cho một biến, mà sau này bạn có thể chuyển dưới dạng paritterer sang clearTimeout () để xóa nó.
Jose Faeti

3
@blackhawk: Tôi chỉ thấy câu trả lời đó và +1 nó; nhưng bạn sẽ biết rằng bạn thực sự không cần phải sử dụng ClearTimeout () tại đây, vì setTimeout () chỉ chạy một lần! Một 'if-other' là vừa đủ: đừng để thực thi vượt qua setTimeout () một khi bạn đã tìm thấy phần tử được chèn.
1111161171159459134

Hướng dẫn thực tế hữu ích để sử dụng MutationObserver(): davidwalsh.name/mutingobserver-api
gfullam

4
Điều đó thật bẩn thỉu. Có thực sự không có "tương đương" với AS3 Event.ADDED_TO_STAGEkhông?
Bitterblue

45

Câu trả lời thực tế là "sử dụng trình quan sát đột biến" (như được nêu trong câu hỏi này: Xác định xem phần tử HTML đã được thêm vào DOM động chưa ), tuy nhiên hỗ trợ (cụ thể trên IE) bị hạn chế ( http://caniuse.com/mutingobserver ) .

Vì vậy, câu trả lời THỰC TẾ thực tế là "Sử dụng các nhà quan sát đột biến .... cuối cùng. Nhưng hãy đi với câu trả lời của Jose Faeti ngay bây giờ" :)


19

Giữa sự phản đối của các sự kiện đột biến và sự xuất hiện của MutationObservermột cách hiệu quả để được thông báo khi một yếu tố cụ thể được thêm vào DOM là khai thác các sự kiện hoạt hình CSS3 .

Để trích dẫn bài viết trên blog:

Thiết lập một chuỗi khung hình CSS nhắm mục tiêu (thông qua lựa chọn CSS của bạn) bất kỳ thành phần DOM nào bạn muốn nhận sự kiện chèn nút DOM cho. Tôi đã sử dụng một thuộc tính css tương đối lành tính và ít được sử dụng, clip tôi đã sử dụng màu phác thảo để tránh gây rối với các kiểu trang dự định - mã đã từng nhắm mục tiêu thuộc tính clip, nhưng nó không còn hoạt động trong IE kể từ phiên bản 11. Điều đó cho biết, bất kỳ tài sản nào có thể hoạt hình sẽ hoạt động, chọn bất kỳ tài sản nào bạn thích.

Tiếp theo tôi đã thêm một trình lắng nghe hoạt hình trên toàn tài liệu mà tôi sử dụng làm đại biểu để xử lý các phần chèn vào nút. Sự kiện hoạt hình có một thuộc tính được gọi là animationName trên đó cho bạn biết chuỗi keyframe nào đã khởi động hoạt hình. Chỉ cần đảm bảo thuộc tính animationName giống với tên chuỗi keyframe mà bạn đã thêm để chèn nút và bạn sẽ ổn.


Đây là giải pháp hiệu suất cao nhất và có một câu trả lời trong một câu hỏi trùng lặp đề cập đến một thư viện cho việc này.
Dan Dascalescu

1
Câu trả lời không nản lòng.
Gotkhan Kurt

Cẩn thận. Nếu độ chính xác mili giây là quan trọng, cách tiếp cận này không chính xác. Jsbin này chứng minh rằng có sự khác biệt hơn 30ms giữa gọi lại nội tuyến và sử dụng animationstart, jsbin.com/netuquralu/1/edit .
Gajus

Tôi đồng ý với kurt, điều này được đánh giá thấp.
Zabbu

13

Bạn có thể sử dụng livequeryplugin cho jQuery. Bạn có thể cung cấp một biểu thức chọn, chẳng hạn như:

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

Mỗi khi một nút của removeItemButtonlớp được thêm vào, một thông báo sẽ xuất hiện trên thanh trạng thái.

Về hiệu quả, bạn có thể muốn tránh điều này, nhưng trong mọi trường hợp, bạn có thể tận dụng plugin thay vì tạo trình xử lý sự kiện của riêng mình.

Xem lại câu trả lời

Câu trả lời ở trên chỉ nhằm phát hiện ra rằng một mục đã được thêm vào DOM thông qua plugin.

Tuy nhiên, rất có thể, một jQuery.on()cách tiếp cận sẽ phù hợp hơn, ví dụ:

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

Ví dụ: nếu bạn có nội dung động đáp ứng các nhấp chuột, tốt nhất là liên kết các sự kiện với bộ chứa chính bằng cách sử dụng jQuery.on.


11

ETA 24 tháng 4 17 Tôi muốn đơn giản hóa điều này một chút với một số async/ awaitphép thuật, vì nó làm cho nó ngắn gọn hơn rất nhiều:

Sử dụng cùng có thể quan sát được:

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

Chức năng gọi điện của bạn có thể đơn giản như:

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

Nếu bạn muốn thêm thời gian chờ, bạn có thể sử dụng một Promise.racemẫu đơn giản như được trình bày ở đây :

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

Nguyên

Bạn có thể làm điều này mà không cần thư viện, nhưng bạn phải sử dụng một số nội dung ES6, vì vậy hãy nhận thức rõ các vấn đề tương thích (ví dụ: nếu đối tượng của bạn chủ yếu là người dùng Amish, luddite hoặc tệ hơn là người dùng IE8)

Trước tiên, chúng tôi sẽ sử dụng API MutingObserver để xây dựng một đối tượng quan sát viên. Chúng tôi sẽ bao bọc đối tượng này trong một lời hứa và resolve()khi cuộc gọi lại được thực hiện (h / t davidwalshblog) bài viết trên blog của david walsh về các đột biến :

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

Sau đó, chúng tôi sẽ tạo ra một generator function. Nếu bạn chưa sử dụng những thứ này, thì bạn đã bỏ lỡ - nhưng tóm tắt ngắn gọn là: nó chạy như một chức năng đồng bộ hóa và khi tìm thấy một yield <Promise>biểu thức, nó sẽ chờ đợi theo cách không bị chặn hoàn thành ( Máy phát điện làm nhiều hơn thế, nhưng đây là điều chúng tôi quan tâm ở đây ).

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

Một phần khó khăn về máy phát điện là chúng không 'trở lại' như một chức năng bình thường. Vì vậy, chúng tôi sẽ sử dụng chức năng trợ giúp để có thể sử dụng trình tạo như một chức năng thông thường. (một lần nữa, h / t để lùn )

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 );
            }
        }
    })();
}

Sau đó, tại bất kỳ thời điểm nào trước khi đột biến DOM dự kiến ​​có thể xảy ra, chỉ cần chạy runGenerator(getMutation).

Bây giờ bạn có thể tích hợp các đột biến DOM vào luồng điều khiển kiểu đồng bộ. Làm thế nào mà ra.


8

Có một thư viện javascript đầy hứa hẹn có tên Đến nơi trông giống như một cách tuyệt vời để bắt đầu tận dụng các nhà quan sát đột biến một khi hỗ trợ trình duyệt trở nên phổ biến.

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


3

Hãy xem plugin này thực sự tuyệt vời đó - jquery.initialize

Nó hoạt động rất giống chức năng .each, sự khác biệt là nó cần bộ chọn bạn đã nhập và xem các mục mới được thêm vào trong tương lai phù hợp với bộ chọn này và khởi tạo chúng

Khởi tạo như thế này

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

Nhưng bây giờ nếu .some-elementbộ chọn khớp phần tử mới sẽ xuất hiện trên trang, nó sẽ được khởi tạo ngay lập tức.

Cách thêm mục mới không quan trọng, bạn không cần quan tâm đến bất kỳ cuộc gọi lại nào, v.v.

Vì vậy, nếu bạn thêm yếu tố mới như:

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

nó sẽ được khởi tạo ngay lập tức.

Plugin dựa trên MutationObserver


0

Một giải pháp javascript thuần túy (không có 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'));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.