Làm cho hàm chờ cho đến khi phần tử tồn tại


158

Tôi đang cố gắng thêm một canvas trên một canvas khác - làm thế nào tôi có thể làm cho chức năng này chờ để bắt đầu cho đến khi canvas đầu tiên được tạo?

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
Khi bạn tạo canvas khi nhấp, hãy chạy chức năng hoặc kích hoạt một sự kiện chạy trình xử lý chạy chức năng. không có sự kiện trình duyệt chéo tích hợp nào xảy ra khi một phần tử có sẵn.
Kevin B

Câu trả lời:


302

Nếu bạn có quyền truy cập vào mã tạo canvas - chỉ cần gọi hàm ngay sau khi canvas được tạo.

Nếu bạn không có quyền truy cập vào mã đó (ví dụ: Nếu đó là mã của bên thứ 3 như google maps) thì điều bạn có thể làm là kiểm tra sự tồn tại trong một khoảng:

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

Nhưng lưu ý - nhiều lần mã bên thứ 3 có tùy chọn kích hoạt mã của bạn (bằng cách gọi lại hoặc kích hoạt sự kiện) khi kết thúc tải. Đó có thể là nơi bạn có thể đặt chức năng của bạn. Giải pháp khoảng thời gian thực sự là một giải pháp tồi và chỉ nên được sử dụng nếu không có gì khác hoạt động.


giải pháp hoàn hảo để sử dụng trong typeahead angularjs. Cảm ơn đã hướng dẫn tôi đi đúng hướng!
JuanTrev

1
Giải pháp tuyệt vời để chờ đợi một thứ được tạo ra bởi Ajax trước khi đưa một số thứ khác vào đó. Cảm ơn rất nhiều.
Countzero

@iftah Làm thế nào để tôi làm việc này nếu bộ chọn là một biến? Ngoài ra, nếu đó là một bộ chọn ID hoặc Class cũng thay đổi. Đôi khi có nhiều phần tử được trả về khi tôi chọn với một lớp và tôi sẽ cần tìm cách chuyển một chỉ mục đến bộ chọn để tìm ra cái nào. Làm thế nào tôi sẽ làm điều này? Cảm ơn
Kragalon

@Kraglon đây là một câu hỏi hoàn toàn khác và không phù hợp với ý kiến ​​của câu trả lời này. Tôi đề nghị bạn hỏi một câu hỏi mới, giải thích những gì bạn đã cố gắng, vấn đề là gì, v.v ...
Iftah 17/03/2016

8
Một điều nữa là rất quan trọng để đề cập khi sử dụng các giải pháp được đưa ra, bạn nên có những đoạn mã bên trong một vòng lặp for và thiết lập một bộ đếm tối đa retry, khi gặp sự cố bạn không kết thúc với một vòng lặp vô hạn :)
BJ

48

Tùy thuộc vào trình duyệt nào bạn cần hỗ trợ, có tùy chọn M mutObserver .

EDIT: Tất cả các trình duyệt chính hỗ trợ MutingObserver ngay bây giờ .

Một cái gì đó dọc theo dòng này nên thực hiện các mẹo:

// 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 Tôi đã không tự mình kiểm tra mã này, nhưng đó là ý tưởng chung.

Bạn có thể dễ dàng mở rộng phần này để chỉ tìm kiếm phần DOM đã thay đổi. Đối với điều đó, sử dụng mutationsđối số, đó là một mảng các MutationRecordđối tượng.


2
Yêu cái này Cảm ơn bạn.
phù hiệu

1
Mẫu này thực sự hữu ích trong nhiều trường hợp, đặc biệt là nếu bạn đang kéo JS vào một trang và không biết các mục khác có được tải không.
Cho tên

1
Câu trả lời tốt nhất! Cảm ơn!
Antony Hatchkins

1
Tôi bị mắc kẹt với một trình duyệt cũ (ff38) và điều này đã cứu tôi.
jung rhew 17/10/19

1
Thật đáng kinh ngạc! Tôi ước tôi biết điều này tồn tại sớm hơn.
Cướp

39

Điều này sẽ chỉ hoạt động với các trình duyệt hiện đại nhưng tôi thấy dễ dàng hơn khi chỉ sử dụng một thenvì vậy vui lòng kiểm tra trước nhưng:

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

Hoặc sử dụng các chức năng của trình tạo

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

Sử dụng

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

Có một lỗi. Khi sử dụng các hàm tạo, truy vấnSelector phải được cập nhật trong mỗi vòng lặp:while (document.querySelector(selector) === null) {await rafAsync()}
haofly

31

Một cách tiếp cận hiện đại hơn để chờ đợi các yếu tố:

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

Lưu ý rằng mã này sẽ cần được bọc trong một hàm async .


4
cái này khá gọn gàng!
Dexter Bengil

Có gì rở đây
Daniel Möller

Chà, ok, nhưng nó đến từ đâu? Nó làm gì? Bạn đang gửi setTimeoutgì đến ?
Daniel Möller

@ DanielMöller bạn có thể cần xem qua Promise để hiểu rõ hơn về mã này. Về cơ bản những gì mã làm ở đây là thiết lập thời gian chờ là 500ms và đợi cho nó hoàn thành trước khi khởi động một vòng lặp mới của vòng lặp while. Giải pháp thông minh!
ClementParis016

8

Đây là một cải tiến nhỏ so với câu trả lời của Jamie Hutber

const checkElement = async selector => {

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

return document.querySelector(selector); };

8

Là tốt hơn để chuyển tiếp trong requestAnimationFramehơn trong một setTimeout. đây là giải pháp của tôi trong các mô-đun es6 và sử dụngPromises .

es6, mô-đun và lời hứa:

// 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

Tôi nghĩ rằng tôi đã bỏ lỡ một sự trở lại ... trước khi Promise mới.
ncubica

1
Đây là giải pháp thích hợp, tốt hơn nhiều so với tất cả các kiểm tra dựa trên Timer định kỳ.
András Szepesházi

4
Trên thực tế, điều này không hoạt động ở dạng hiện tại của nó. Nếu $ someE bổ sung ban đầu là null (nghĩa là chưa có trong DOM), thì bạn chuyển giá trị null này (thay vì bộ chọn CSS) cho hàm onEuityReady của bạn và phần tử sẽ không bao giờ được giải quyết. Thay vào đó, hãy chuyển bộ chọn CSS dưới dạng văn bản và cố gắng tham chiếu đến phần tử thông qua .querySelector trên mỗi lần truyền.
András Szepesházi

1
Điều này làm việc rất tốt cho trường hợp sử dụng của tôi và có vẻ là một giải pháp thích hợp cho tôi. Cảm ơn
24

5

Đây là một giải pháp sử dụng đài quan sát.

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

Bây giờ bạn có thể viết

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

4

Bạn có thể kiểm tra xem dom đã tồn tại hay chưa bằng cách đặt thời gian chờ cho đến khi nó được hiển thị trong dom.

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

2

Một biến thể khác của 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);

Chỉ trong trường hợp phần tử không bao giờ được hiển thị, vì vậy chúng tôi không kiểm tra vô hạn.


2

Nếu bạn muốn một giải pháp chung sử dụng MutingObserver, bạn có thể sử dụng chức năng này

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

Nguồn: https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
Ví dụ cách sử dụng

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

Lưu ý: MutingObserver có hỗ trợ trình duyệt tuyệt vời; https://caniuse.com/#feat=mutingobserver

Et voilà! :)

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.