ทำให้ฟังก์ชันรอจนกระทั่งมีองค์ประกอบอยู่


159

ฉันกำลังพยายามเพิ่มผืนผ้าใบบนผืนผ้าใบอีกผืน - ฉันจะทำให้ฟังก์ชั่นนี้รอเพื่อเริ่มต้นจนกว่าจะสร้างผืนผ้าใบแรกได้อย่างไร

function PaintObject(brush) {

    this.started = false;

    // get handle of the main canvas, as a DOM object, not as a jQuery Object. Context is unfortunately not yet
    // available in jquery canvas wrapper object.
    var mainCanvas = $("#" + brush).get(0);

    // Check if everything is ok
    if (!mainCanvas) {alert("canvas undefined, does not seem to be supported by your browser");}
    if (!mainCanvas.getContext) {alert('Error: canvas.getContext() undefined !');}

    // Get the context for drawing in the canvas
    var mainContext = mainCanvas.getContext('2d');
    if (!mainContext) {alert("could not get the context for the main canvas");}

    this.getMainCanvas = function () {
        return mainCanvas;
    }
    this.getMainContext = function () {
        return mainContext;
    }

    // Prepare a second canvas on top of the previous one, kind of second "layer" that we will use
    // in order to draw elastic objects like a line, a rectangle or an ellipse we adjust using the mouse
    // and that follows mouse movements
    var frontCanvas = document.createElement('canvas');
    frontCanvas.id = 'canvasFront';
    // Add the temporary canvas as a second child of the mainCanvas parent.
    mainCanvas.parentNode.appendChild(frontCanvas);

    if (!frontCanvas) {
        alert("frontCanvas null");
    }
    if (!frontCanvas.getContext) {
        alert('Error: no frontCanvas.getContext!');
    }
    var frontContext = frontCanvas.getContext('2d');
    if (!frontContext) {
        alert("no TempContext null");
    }

    this.getFrontCanvas = function () {
        return frontCanvas;
    }
    this.getFrontContext = function () {
        return frontContext;
    }

4
เมื่อคุณสร้าง Canvas เมื่อคลิกให้เรียกใช้ฟังก์ชันหรือเรียกใช้เหตุการณ์ที่เรียกใช้ตัวจัดการที่เรียกใช้ฟังก์ชัน ไม่มีเหตุการณ์ข้ามเบราว์เซอร์ในตัวที่เกิดขึ้นเมื่อองค์ประกอบพร้อมใช้งาน
Kevin B

คำตอบ:


303

หากคุณมีสิทธิ์เข้าถึงรหัสที่สร้างพื้นที่วาดภาพ - เพียงเรียกใช้ฟังก์ชันที่อยู่ตรงนั้นหลังจากสร้างพื้นที่วาดรูปแล้ว

หากคุณไม่สามารถเข้าถึงรหัสนั้นได้ (เช่นหากเป็นรหัสบุคคลที่สามเช่นแผนที่ google) สิ่งที่คุณสามารถทำได้คือทดสอบการมีอยู่ในช่วงเวลา:

var checkExist = setInterval(function() {
   if ($('#the-canvas').length) {
      console.log("Exists!");
      clearInterval(checkExist);
   }
}, 100); // check every 100ms

แต่โปรดทราบ - รหัสบุคคลที่สามหลายครั้งมีตัวเลือกให้เปิดใช้งานรหัสของคุณ (โดยการโทรกลับหรือการทริกเกอร์เหตุการณ์) เมื่อโหลดเสร็จ นั่นอาจเป็นที่ที่คุณสามารถใส่ฟังก์ชั่นของคุณ การแก้ปัญหาช่วงเวลาเป็นทางออกที่ไม่ดีและควรใช้เฉพาะเมื่อไม่มีสิ่งใดทำงานได้อีก


โซลูชั่นที่สมบูรณ์แบบสำหรับการใช้งานในหัวพิมพ์เชิงมุม ขอบคุณที่ชี้นำฉันในทิศทางที่ถูกต้อง!
JuanTrev

1
ทางออกที่ยอดเยี่ยมที่จะรอบางสิ่งที่อาแจ็กซ์สร้างขึ้นก่อนที่จะใส่สิ่งอื่นเข้าไปในนั้น ขอบคุณมาก.
Countzero

@iftah ฉันจะทำให้มันทำงานอย่างไรถ้าตัวเลือกเป็นตัวแปร? นอกจากนี้หากเป็นตัวเลือก ID หรือ Class ก็เปลี่ยนไปเช่นกัน บางครั้งมีองค์ประกอบหลายอย่างถูกส่งคืนเมื่อฉันเลือกคลาสและฉันต้องการค้นหาวิธีการส่งดัชนีไปยังตัวเลือกเพื่อหาว่าองค์ประกอบใด ฉันจะทำสิ่งนี้ได้อย่างไร ขอบคุณ
Kragalon

@Kraglon นี้เป็นคำถามที่แตกต่างอย่างสิ้นเชิงและไม่เหมาะสำหรับความเห็นของคำตอบนี้ ฉันขอแนะนำให้คุณถามคำถามใหม่อธิบายสิ่งที่คุณลองสิ่งที่เป็นปัญหา ฯลฯ ...
Iftah

8
อีกสิ่งหนึ่งที่สำคัญที่ต้องพูดถึงเมื่อใช้โซลูชันที่กำหนดคุณควรมีโค้ดในวง for และตั้งค่าตัวนับการลองใหม่สูงสุดหากมีสิ่งผิดปกติเกิดขึ้นคุณไม่ได้จบด้วย infinity loop :)
BJ

48

ทั้งนี้ขึ้นอยู่กับเบราว์เซอร์ที่คุณจำเป็นต้องสนับสนุนการมีตัวเลือกของMutationObserver

แก้ไข: เบราว์เซอร์หลักทั้งหมดรองรับ MutationObserverทันที

สิ่งที่อยู่ในแนวของสิ่งนี้ควรทำเคล็ดลับ:

// callback executed when canvas was found
function handleCanvas(canvas) { ... }

// set up the mutation observer
var observer = new MutationObserver(function (mutations, me) {
  // `mutations` is an array of mutations that occurred
  // `me` is the MutationObserver instance
  var canvas = document.getElementById('my-canvas');
  if (canvas) {
    handleCanvas(canvas);
    me.disconnect(); // stop observing
    return;
  }
});

// start observing
observer.observe(document, {
  childList: true,
  subtree: true
});

NB ฉันยังไม่ได้ทดสอบรหัสนี้ด้วยตัวเอง แต่นั่นเป็นแนวคิดทั่วไป

คุณสามารถขยายได้อย่างง่ายดายเพื่อค้นหาเฉพาะส่วนของ DOM ที่เปลี่ยนแปลง เพื่อที่จะใช้mutationsอาร์กิวเมนต์มันเป็นอาร์เรย์ของMutationRecordวัตถุ


2
ชอบอันนี้ ขอบคุณ.
insign

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

1
คำตอบที่ดีที่สุด! ขอบคุณ!
Antony Hatchkins

1
ฉันติดอยู่กับเบราว์เซอร์เก่า (ff38) และสิ่งนี้ช่วยฉัน
jung rhew

1
สิ่งนี้ช่างมหัศจรรย์! ฉันหวังว่าฉันจะรู้ว่าสิ่งนี้มีอยู่ก่อนหน้านี้
Rob

39

สิ่งนี้จะใช้ได้กับเบราว์เซอร์ที่ทันสมัย ​​แต่ฉันคิดว่ามันง่ายกว่าที่จะใช้thenดังนั้นโปรดทดสอบก่อน แต่:

รหัส

function rafAsync() {
    return new Promise(resolve => {
        requestAnimationFrame(resolve); //faster than set time out
    });
}

function checkElement(selector) {
    if (document.querySelector(selector) === null) {
        return rafAsync().then(() => checkElement(selector));
    } else {
        return Promise.resolve(true);
    }
}

หรือใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้า

async function checkElement(selector) {
    const querySelector = document.querySelector(selector);
    while (querySelector === null) {
        await rafAsync()
    }
    return querySelector;
}  

การใช้

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});

มีข้อผิดพลาด เมื่อใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้า querySelector ควรได้รับการปรับปรุงในทุกห่วง:while (document.querySelector(selector) === null) {await rafAsync()}
haofly

31

วิธีการที่ทันสมัยกว่าในการรอองค์ประกอบ:

while(!document.querySelector(".my-selector")) {
  await new Promise(r => setTimeout(r, 500));
}
// now the element is loaded

โปรดทราบว่ารหัสนี้จะต้องมีการห่อในฟังก์ชั่น async


4
นี่มันเรียบร้อยดี!
เบ็กซ์เด็กซ์เตอร์

คืออะไรrที่นั่น?
Daniel Möller

โอเค แต่มันมาจากไหน มันทำอะไร? คุณส่งsetTimeoutอะไรไป
Daniel Möller

@ DanielMöllerคุณอาจต้องใช้เวลาดูที่สัญญาจะได้รับความรู้ความเข้าใจของรหัสนี้ โดยทั่วไปสิ่งที่รหัสทำที่นี่คือการตั้งค่าการหมดเวลาของ 500ms และรอให้เสร็จสมบูรณ์ก่อนที่จะเตะซ้ำใหม่ของห่วงในขณะที่ ทางออกที่ฉลาด!
ClementParis016

8

นี่คือการปรับปรุงเล็กน้อยกับคำตอบของ Jamie Hutber

const checkElement = async selector => {

while ( document.querySelector(selector) === null) {
    await new Promise( resolve =>  requestAnimationFrame(resolve) )
}

return document.querySelector(selector); };

8

ดีกว่าที่จะถ่ายทอดในกว่าในrequestAnimationFrame setTimeoutนี่เป็นวิธีการแก้ปัญหาของฉันในโมดูล ES6 Promisesและการใช้

es6, โมดูลและสัญญา:

// onElementReady.js
const onElementReady = $element => (
  new Promise((resolve) => {
    const waitForElement = () => {
      if ($element) {
        resolve($element);
      } else {
        window.requestAnimationFrame(waitForElement);
      }
    };
    waitForElement();
  })
);

export default onElementReady;

// in your app
import onElementReady from './onElementReady';

const $someElement = document.querySelector('.some-className');
onElementReady($someElement)
  .then(() => {
    // your element is ready
  }

plain js and promises:

var onElementReady = function($element) {
  return new Promise((resolve) => {
    var waitForElement = function() {
      if ($element) {
        resolve($element);
      } else {
        window.requestAnimationFrame(waitForElement);
      }
    };
    waitForElement();
  })
};

var $someElement = document.querySelector('.some-className');
onElementReady($someElement)
  .then(() => {
    // your element is ready
  });

Uncaught TypeError: Cannot read property 'then' of undefined
Jeff Puckett

ฉันคิดว่าฉันพลาดการกลับมา ... ก่อนสัญญาใหม่
ncubica

1
นี่เป็นวิธีการแก้ปัญหาที่เหมาะสมดีกว่าการตรวจสอบตามกำหนดเวลาเป็นระยะ
AndrásSzepesházi

4
ที่จริงแล้วสิ่งนี้ไม่ทำงานในรูปแบบปัจจุบัน ถ้า $ someElement ในตอนแรกเป็นโมฆะ (เช่นยังไม่มีใน DOM) คุณจะผ่านค่า Null นี้ (แทนที่จะเป็นตัวเลือก CSS) ไปยังฟังก์ชัน onElementReady ของคุณและองค์ประกอบจะไม่สามารถแก้ไขได้ ให้ส่งตัวเลือก CSS เป็นข้อความแทนและลองรับการอ้างอิงถึงองค์ประกอบผ่าน. querySelector ในแต่ละรอบ
AndrásSzepesházi

1
มันใช้งานได้ดีสำหรับกรณีการใช้งานของฉันและดูเหมือนจะเป็นทางออกที่เหมาะสมสำหรับฉัน ขอบคุณ
rdhaese

5

นี่คือวิธีการแก้ปัญหาโดยใช้สิ่งที่สังเกตได้

waitForElementToAppear(elementId) {                                          

    return Observable.create(function(observer) {                            
            var el_ref;                                                      
            var f = () => {                                                  
                el_ref = document.getElementById(elementId);                 
                if (el_ref) {                                                
                    observer.next(el_ref);                                   
                    observer.complete();                                     
                    return;                                                  
                }                                                            
                window.requestAnimationFrame(f);                             
            };                                                               
            f();                                                             
        });                                                                  
}                                                                            

ตอนนี้คุณสามารถเขียน

waitForElementToAppear(elementId).subscribe(el_ref => doSomethingWith(el_ref);

4

คุณสามารถตรวจสอบว่ามีอยู่แล้ว dom โดยการตั้งค่าการหมดเวลาจนกว่าจะมีการแสดงผลใน dom

var panelMainWrapper = document.getElementById('panelMainWrapper');
setTimeout(function waitPanelMainWrapper() {
    if (document.body.contains(panelMainWrapper)) {
        $("#panelMainWrapper").html(data).fadeIn("fast");
    } else {
        setTimeout(waitPanelMainWrapper, 10);
    }
}, 10);

3

หากคุณต้องการโซลูชันทั่วไปที่ใช้ MutationObserver คุณสามารถใช้ฟังก์ชันนี้

// MIT Licensed
// Author: jwilson8767

/**
 * Waits for an element satisfying selector to exist, then resolves promise with the element.
 * Useful for resolving race conditions.
 *
 * @param selector
 * @returns {Promise}
 */
export function elementReady(selector) {
  return new Promise((resolve, reject) => {
    const el = document.querySelector(selector);
    if (el) {resolve(el);}
    new MutationObserver((mutationRecords, observer) => {
      // Query for elements matching the specified selector
      Array.from(document.querySelectorAll(selector)).forEach((element) => {
        resolve(element);
        //Once we have resolved we don't need the observer anymore.
        observer.disconnect();
      });
    })
      .observe(document.documentElement, {
        childList: true,
        subtree: true
      });
  });
}

ที่มา: https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
ตัวอย่างวิธีใช้

elementReady('#someWidget').then((someWidget)=>{someWidget.remove();});

หมายเหตุ: MutationObserver มีการสนับสนุนเบราว์เซอร์ที่ยอดเยี่ยม https://caniuse.com/#feat=mutationobserver

และอื่น ๆ ! :)


2

อีกรูปแบบของIftah

var counter = 10;
var checkExist = setInterval(function() {
  console.log(counter);
  counter--
  if ($('#the-canvas').length || counter === 0) {
    console.log("by bye!");
    clearInterval(checkExist);
  }
}, 200);

ในกรณีที่องค์ประกอบไม่เคยปรากฏดังนั้นเราจึงไม่ตรวจสอบอนันต์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.