จะรอจนกระทั่งองค์ประกอบมีอยู่ได้อย่างไร


237

ฉันกำลังทำงานกับส่วนขยายใน Chrome และฉันสงสัยว่า: อะไรคือวิธีที่ดีที่สุดในการค้นหาว่าองค์ประกอบใดที่มีอยู่ การใช้จาวาสคริปต์ธรรมดาที่มีช่วงเวลาที่ตรวจสอบจนกว่าจะมีองค์ประกอบอยู่หรือ jQuery มีวิธีง่ายๆในการทำเช่นนี้หรือไม่?


1
ดูเหมือนว่าทุก ๆ ตัวเลือกที่นี่วันนี้ (รวมถึงจากความคิดเห็น) นั้นล้าสมัยหรือไม่สมบูรณ์ พวกเขาไม่พิจารณาอินพุตที่ยอดเยี่ยมของ @ hughsk อย่างเต็มที่อาร์กิวเมนต์ที่เข้ากันได้ ในขณะเดียวกันฉันขอแนะนำเพียงแค่ใช้การอัปเดตของแบรนดอนกับคำตอบของไรอันเพื่อความเรียบง่ายทั่วไปและลดความเสี่ยงของค่าใช้จ่ายฉันคิดว่า
cregox

4
MutationObserver> >DOM Mutation Events setTimeout
mattsven

2
ไม่ใช่จากที่ฉันยืนอยู่ setTimeoutเข้ากันได้ง่ายต่อการใช้งานง่ายต่อการบำรุงรักษาและมีค่าใช้จ่ายเล็กน้อย
cregox

setTimeout+ jQueryน้อยกว่าอุดมคติในความคิดของฉันด้วยเหตุผลสองประการ: 1. ) jQuery bloat 2. ) คุณต้องค้นหา DOM สำหรับองค์ประกอบด้วยตนเองโดยไม่จำเป็นเหตุการณ์ต่าง ๆ จะเอาชนะความเร็วได้อย่างง่ายดาย 3. ) มันจะช้ากว่า native ทั่วไป การดำเนินงาน หากคุณต้องทำอะไรก็ตามโดยขึ้นอยู่กับองค์ประกอบที่มีอยู่อย่างรวดเร็วพอสมควรโดยเฉพาะหากประสบการณ์การใช้งานที่ไร้รอยต่อเป็นเป้าหมายของคุณ
mattsven

3
มีคน 3 ประเภท: ผู้ที่สามารถนับได้และผู้ที่ไม่สามารถ ; P
cregox

คำตอบ:


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 แล้วค่อนข้างซับซ้อนดังนั้นฉันจึงได้สร้างไลบรารีมาถึง. jsเพื่อให้ API ที่ง่ายขึ้นเพื่อฟังการสร้าง / ลบองค์ประกอบ
Uzair Farooq

15
ฉันแนะนำให้ใช้ @UzairFarooq excellent library github.com/uzairfarooq/arrive
Dennis

3
สองสิ่งที่ควรทราบ: (1) จะเป็นการดีกว่าที่จะทำif (mutation.addedNodes.length)ตั้งแต่if (mutation.addedNodes)ยังคงกลับมาจริงแม้ว่าจะเป็นอาร์เรย์ว่างเปล่า (2) คุณไม่สามารถทำได้mutation.addedNodes.forEach()เพราะ addNodes เป็น nodeList และคุณไม่สามารถทำซ้ำผ่าน nodeList ด้วย forEach ได้ สำหรับวิธีการแก้ปัญหานี้โปรดดูtoddmotto.com/ditch-the-array-foreach-call-nodelist-hack
thdoan

3
คุณสามารถยกตัวอย่างว่าจะใช้สิ่งนี้ได้อย่างไร? ไม่แน่ใจว่าจะใส่ตัวเลือก jquery ของฉันหรือรหัสที่ฉันต้องการดำเนินการเมื่อองค์ประกอบ DOM อยู่
Superdooperhero

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 เกินไป ... ;)
knutole

4
บางทีคุณควรพูดถึงวิธีการทำงาน: ใช้งานได้โดยถามทุก ๆ 500 มิลลิวินาทีหากองค์ประกอบนั้นมีอยู่ (ใช้ a window.setInterval) ฉันไม่รู้ว่าMutationObserverคำตอบนั้นใช้ได้ดีกับการสำรวจหรือไม่ ...
กีฬา

2
มันไม่ทำงานอย่างถูกต้องหากองค์ประกอบอยู่ในหน้า นี่คือรุ่นที่เหมาะสมของฟังก์ชันนี้: gist.github.com/PizzaBrandon/5709010
Roland Soós

2
คุณช่วยอธิบายสิ่งที่ใช้;ในการเริ่มต้นของฟังก์ชั่น ( ;(function ($, window) {) ได้อย่างไร?
mrid

76

นี่คือฟังก์ชั่นหลักของจาวาสคริปต์เพื่อรอการแสดงองค์ประกอบ

พารามิเตอร์:

  1. selector: ฟังก์ชั่นนี้จะมองหาองค์ประกอบ $ {selector}
  2. 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"ทุก ๆ 5,000 มิลลิวินาที


ดี! คุณสามารถเขียนสิ่งนี้เพื่อให้ตัวเลือกใด ๆ สามารถยอมรับได้?
mattsven

ฉันสงสัยว่าฉันสามารถทำได้ .. แต่โปรดดูที่โพสต์นี้เพื่อรับ getElementByXpath: stackoverflow.com/questions/10596417/ …
Etienne Tonnelier

1
เกี่ยวกับquerySelectorอะไร developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
mattsven

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)ควรเรียกใช้ฟังก์ชันหลังจากองค์ประกอบที่มี ID 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()มันก็จะไม่ทำงานกับองค์ประกอบที่สร้างขึ้นแบบไดนามิกแม้ใน
Richard Neil Ilagan

7
@Bery ดังที่ Richard ชี้ให้เห็นนี่ใช้งานได้เฉพาะองค์ประกอบที่มีอยู่แล้วใน HTML เมื่อมีการร้องขอจากเซิร์ฟเวอร์เป็นครั้งแรก หากมีการใช้ Javascript เพื่อเพิ่มองค์ประกอบให้กับ DOM แบบไดนามิกมันจะไม่ทำงาน
Chandranshu

6
@ Sam, คุณช่วยอธิบายให้ชัดเจนได้อย่างไรว่ามันแนบไปกับการอ้างอิงขององค์ประกอบในหน่วยความจำได้หรือไม่?
Vikas Singhal

3
คำตอบนี้ไม่ถูกต้อง สิ่งที่คุณกำลังตรวจสอบจริง ๆ ที่นี่เป็นประจำ$(document).ready()ไม่ใช่องค์ประกอบที่คุณคิดว่ามันจะใช้เช่นกัน นั่นเป็นเพียงวิธีการฟังพิเศษนี้ทำงาน ตัวอย่าง
Shikkediel

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 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
มีภายในเห็นด้วยนี้เป็นวิธีที่สะอาดมากและเหมาะกับฉัน
jstafford

3
คำตอบนี้ใช้ได้กับ IE8-10 เช่นเดียวกับเบราว์เซอร์ที่ทันสมัย ปัญหาหลักคือมันจะยังคงทำงานต่อไปหากองค์ประกอบนั้นไม่มีอยู่ - ดังนั้นจึงเป็นการดีที่สุดเมื่อคุณแน่ใจว่าองค์ประกอบนั้นจะอยู่ที่นั่น มิฉะนั้นคุณสามารถเพิ่มเคาน์เตอร์
สำหรับชื่อ

1
ทำงานอย่างสมบูรณ์แบบสำหรับฉัน
James Stewart

1
ทำงานเหมือนมีเสน่ห์ !!
Aman

1
พวกเขาเหมือนกันไม่เหมือนกัน นอกจากนี้หลายคนกำลังทำเช่นเดียวกัน สุดท้ายฉันเขียนรหัสนี้ด้วยตนเอง นั่นคือเหตุผลที่ไม่ดีอย่างไรก็ตามแม้ว่ามันจะเป็นกรณีจริง ๆ ฉันก็ขอขอบคุณความคิดเห็นที่แจ้งให้ฉันทราบ คำตอบแก้ไขปัญหาของ OP และไม่มีแรงจูงใจที่ชัดเจนที่จะถูกลดระดับลง
Diego Fortes

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

ที่นี่เราตรวจสอบทุก ๆ 500ms เพื่อดูว่ามีการโหลดองค์ประกอบเมื่อใดเราสามารถใช้งานได้

สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับการเพิ่มตัวจัดการการคลิกไปยังองค์ประกอบที่เพิ่มไปยังเอกสารแบบไดนามิก


8

วิธีการเกี่ยวกับinsertionQueryห้องสมุด?

insertionQuery ใช้การเรียกกลับ CSS Animation ที่แนบมากับตัวเลือกที่ระบุเพื่อเรียกใช้การเรียกกลับเมื่อองค์ประกอบถูกสร้างขึ้น วิธีนี้ช่วยให้สามารถเรียกใช้การเรียกกลับได้ทุกครั้งที่มีการสร้างองค์ประกอบไม่ใช่เพียงครั้งแรก

จาก GitHub:

วิธีที่ไม่ใช่ dom-event ในการตรวจจับโหนดที่ปรากฏขึ้น และใช้ตัวเลือก

ไม่เพียง แต่รองรับเบราว์เซอร์ที่กว้างขึ้นเท่านั้นมันยังดีกว่า DOMMutationObserver สำหรับบางสิ่ง

ทำไม?

  • เนื่องจากกิจกรรม DOM ทำให้เบราว์เซอร์ช้าลงและ insertionQuery จะไม่ทำงาน
  • เนื่องจากผู้สังเกตการณ์การเปลี่ยนแปลง DOM มีการสนับสนุนเบราว์เซอร์น้อยกว่าการแทรกคำสืบค้น
  • เพราะด้วย insertionQuery คุณสามารถกรองการเปลี่ยนแปลง DOM โดยใช้ตัวเลือกโดยไม่มีประสิทธิภาพเหนือกว่า!

การสนับสนุนอย่างกว้างขวาง!

IE10 + และส่วนใหญ่เป็นอย่างอื่น (รวมถึงมือถือ)


7

นี่คือฟังก์ชั่นที่ทำหน้าที่เป็น wrapper เล็ก ๆ รอบ ๆ ข้อกำหนดเพียงอย่างเดียวคือเบราว์เซอร์รองรับ 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

นี่คือวิธีแก้ปัญหาการคืนสัญญาใน Javilla วานิลลา (ไม่มีการโทรกลับยุ่ง) โดยค่าเริ่มต้นจะตรวจสอบทุก 200ms

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 ล้วนๆที่ช่วยให้คุณรออะไรก็ได้ ตั้งช่วงเวลาให้นานขึ้นเพื่อใช้ทรัพยากร CPU น้อยลง

/**
 * @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+ ที่ใช้งานร่วมกันได้)

สำหรับองค์ประกอบเดียว (องค์ประกอบประเภท):

"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?
Leeroy

1
คุณยกประเด็นที่น่าสนใจ ฉันควรทำให้มันกว้างขึ้นโดยใช้Elementแทน (แก้ไข) ฉันเพียงแค่ทำให้การตรวจสอบเพราะผมต้องการให้แน่ใจว่าตัวแปรelementมีคุณสมบัติinnerHTMLเป็นเอกสารธาตุ MDN รัฐ อย่าลังเลที่จะลบมันถ้าคุณไม่สนใจมัน!
อันวาร์

2

ตัวอย่างที่สะอาดกว่าโดยใช้ MutationObserver:

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

2

นี่เป็นวิธีแก้ปัญหาง่ายๆสำหรับผู้ที่เคยชินกับสัญญาและไม่ต้องการใช้ libs หรือตัวจับเวลาบุคคลที่สามใด ๆ

ฉันใช้มันในโครงการมาระยะหนึ่งแล้ว

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

หรือด้วย async / รอ

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

นี่มันเรียบร้อย! ส่วนที่น่าสนใจเกี่ยวกับมันคือคุณสามารถใช้มันด้วยasync/ awaitเกินไป คุณอาจสามารถบีบการทำงานออกมาได้มากขึ้นโดยทำเช่นนั้นmutations.addedNodes.find(node => node.matchesSelector("..."))
mattsven

@mattsven จุดดี! การตรวจสอบเพียงโหนดในการกลายพันธุ์นั้นมีประสิทธิภาพมากกว่าการทำ document.querySelector
ยองวัง

โปรดแก้ไขการสะกดผิด watiForElm เพื่อ waitForElm
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

หากคุณมีการเปลี่ยนแปลง async dom ฟังก์ชั่นนี้จะตรวจสอบ (โดยมีการ จำกัด เวลาเป็นวินาที) สำหรับองค์ประกอบ 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 });
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.