Заставить функцию ждать, пока элемент не существует

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
Когда вы создаете холст по щелчку, запускаете функцию или запускаете событие, запускающее обработчик, который запускает функцию. нет встроенного кросс-браузерного события, которое происходит, когда элемент становится доступным.
Кевин Б.
Возможный дубликат Как ждать, пока элемент не существует?
user2284570

Ответы:

305

Если у вас есть доступ к коду, который создает холст - просто вызовите функцию сразу после создания холста.

Если у вас нет доступа к этому коду (например, если это сторонний код, такой как Google Maps), то вы можете проверить его на наличие интервала:

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

Но обратите внимание - во многих случаях сторонний код имеет возможность активировать ваш код (путем обратного вызова или запуска события), когда он заканчивает загрузку. Это может быть, где вы можете поставить свою функцию. Интервальное решение - действительно плохое решение и должно использоваться, только если ничего не работает.

Ифты
источник
идеальное решение для использования в angularjs typeahead. Спасибо за руководство в правильном направлении!
JuanTrev
1
Отличное решение для ожидания чего-то, созданного Ajax, прежде чем помещать туда что-то другое. Большое спасибо.
Countzero
@iftah Как мне заставить это работать, если селектор является переменной? Кроме того, если это идентификатор или селектор класса также меняется. Иногда, когда я выбираю классом, возвращаются несколько элементов, и мне нужно найти способ передать индекс селектору, чтобы выяснить, какой именно. Как бы я это сделал? Спасибо
Крагалон
@Kraglon это совершенно другой вопрос и не подходит для комментариев к этому ответу. Я предлагаю вам задать новый вопрос, объяснить, что вы пробовали, в чем проблема и т. Д.
Ифта
8
При использовании данного решения важно упомянуть еще одну вещь: у вас должен быть этот фрагмент кода внутри цикла for и установлен максимальный счетчик повторов, если что-то пойдет не так, вы не получите бесконечный цикл :)
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объектов.

DAMD
источник
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
32

Более современный подход к ожиданию элементов:

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

Обратите внимание, что этот код должен быть заключен в асинхронную функцию .


источник
4
это довольно аккуратно!
Декстер Бенгил
Что rтам?
Даниэль Мёллер
Ну, хорошо, но откуда это? Что оно делает? Что вы отправляете setTimeout?
Даниэль Мёллер
@ DanielMöller, вам может понадобиться взглянуть на Promises, чтобы лучше понять этот код. По сути, здесь код устанавливает тайм-аут в 500 мс и ждет его завершения, прежде чем запустить новую итерацию цикла while. Умное решение!
ClementParis016
8

Вот небольшое улучшение по сравнению с ответом Джейми Хатбер

const checkElement = async selector => {

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

return document.querySelector(selector); };
WLC
источник
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
  });
ncubica
источник
Uncaught TypeError: Cannot read property 'then' of undefined
Джефф Пакетт
Я думаю, что пропустил возвращение ... до нового обещания.
ncubica
1
Это правильное решение, намного лучше, чем все периодические проверки по таймеру.
Андраш Шепешази
4
На самом деле, это не работает в его нынешнем виде. Если $ someElement изначально имеет значение null (т.е. еще не присутствует в DOM), то вы передаете это нулевое значение (вместо селектора CSS) в функцию onElementReady, и элемент никогда не будет разрешен. Вместо этого передайте селектор CSS в виде текста и попытайтесь получить ссылку на элемент через .querySelector на каждом проходе.
Андраш Шепешази
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

И вуаля ! :)

rdhainaut
источник
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);

На всякий случай элемент никогда не отображается, поэтому мы не проверяем бесконечно.

Хериберто Перес
источник