Змусити функцію чекати, поки існує елемент


158

Я намагаюся додати полотно над іншим полотном - як я можу змусити цю функцію чекати, доки не буде створено перше полотно?

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
Коли ви створюєте полотно при натисканні, запустіть функцію або запустіть подію, на якій працює обробник, який виконує функцію. не існує вбудованої крос-браузерної події, яка відбувається, коли елемент стає доступним.
Кевін Б

Відповіді:


303

Якщо у вас є доступ до коду, який створює полотно - просто зателефонуйте до функції відразу після створення полотна.

Якщо у вас немає доступу до цього коду (наприклад, якщо це сторонній код, наприклад, google maps), тоді ви можете зробити тест на наявність інтервалу:

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

Але зауважте - багато разів сторонній код має можливість активувати ваш код (шляхом зворотного дзвінка або ініціювання події), коли він закінчується завантаженням. Тут ви можете поставити свою функцію. Інтервальне рішення - це дійсно погане рішення, і його слід використовувати лише в тому випадку, якщо нічого іншого не працює.


ідеальне рішення для використання в наборах англджей. Дякуємо, що направили мене в правильному напрямку!
JuanTrev

1
Відмінне рішення почекати чогось створить Ajax, перш ніж помістити туди якусь іншу річ. Дуже дякую.
Графзеро

@iftah Як я можу змусити це працювати, якщо селектор є змінною? Крім того, якщо це ідентифікатор чи клас, зміни також. Іноді при виборі з класом повертається кілька елементів, і мені потрібно знайти спосіб передати індекс селектору, щоб зрозуміти, який з них. Як би я це зробив? Спасибі
Крагалон

@Kraglon - це зовсім інше питання і не підходить для коментарів до цієї відповіді. Я пропоную вам задати нове запитання, пояснити, що ви спробували, в чому проблема, тощо ...
Іфтах

8
Ще одне, що важливо зазначити, використовуючи дане рішення, ви повинні мати цей фрагмент коду всередині циклу для циклу та встановити максимальний лічильник повтору, якщо щось піде не так, ви не закінчитеся з циклом нескінченності :)
BJ

48

Залежно від того, який браузер потрібно підтримувати, є можливість MutationObserver .

EDIT: Усі основні браузери зараз підтримують 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
Полюбила цю. Дякую.
вступ

1
Цей візерунок дуже корисний у багатьох випадках, особливо якщо ви перетягуєте JS на сторінку і не знаєте, чи завантажуються інші елементи.
Для імені

1
Найкраща відповідь! Дякую!
Антоні Хеткінс

1
Я затримався зі старим браузером (ff38), і це врятувало мене.
рент джунг

1
Це дивно! Я б хотів, щоб я знав, що це існувало раніше.
Роб

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

Зауважте, що цей код потрібно було б загорнути у функцію асинхронізації .


4
це досить акуратно!
Декстер Бенгіл


Ну гаразд, але звідки воно береться? Що це робить? До чого ви надсилаєте setTimeout?
Даніель Мьоллер

@ DanielMöller Вам може знадобитися поглянути на Обіцяння, щоб краще зрозуміти цей код. В основному, код тут робить налаштування тайм-ауту в 500 мс і чекати його завершення, перш ніж натиснути нову ітерацію циклу while. Розумне рішення!
ClementParis016

8

Ось незначне покращення щодо відповіді Джеймі Хатбера

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
Джефф Пукет

Я думаю, що я пропустив повернення ... перед новим Обіцянням.
ncubica

1
Це правильне рішення, набагато краще, ніж усі періодичні перевірки на основі таймера.
András Szepesházi

4
Насправді це не працює в його нинішньому вигляді. Якщо $ someElement спочатку є нульовим (тобто ще не присутній у DOM), то ви передаєте це нульове значення (замість селектора CSS) у свою функцію onElementReady, і елемент ніколи не буде вирішений. Замість цього передайте селектор CSS як текст і спробуйте отримати посилання на елемент через .querySelector при кожному пропуску.
András Szepeshá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

Et voilà! :)


2

Ще одна варіація Іфти

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

На всякий випадок, якщо елемент ніколи не відображається, тому ми не перевіряємо нескінченно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.