Spraw, aby funkcja czekała, aż element zaistnieje

158

Próbuję dodać płótno na innym kanwie - jak mogę sprawić, by ta funkcja czekała na uruchomienie, aż zostanie utworzona pierwsza kanwa?

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;
    }
Steven
źródło
4
Kiedy tworzysz kanwę po kliknięciu, uruchom funkcję lub wywołaj zdarzenie, które uruchamia procedurę obsługi, która uruchamia funkcję. nie ma wbudowanego zdarzenia między przeglądarkami, które ma miejsce, gdy element staje się dostępny.
Kevin B
możliwy duplikat Jak czekać, aż element zaistnieje?
user2284570

Odpowiedzi:

303

Jeśli masz dostęp do kodu, który tworzy płótno - po prostu wywołaj funkcję zaraz po utworzeniu płótna.

Jeśli nie masz dostępu do tego kodu (np. Jeśli jest to kod strony trzeciej, taki jak mapy Google), możesz przetestować jego istnienie w określonym przedziale czasu:

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

Ale uwaga - wiele razy kod strony trzeciej ma opcję aktywacji kodu (przez wywołanie zwrotne lub wyzwalanie zdarzenia) po zakończeniu ładowania. To może być miejsce, w którym możesz umieścić swoją funkcję. Rozwiązanie interwałowe jest naprawdę złym rozwiązaniem i powinno być używane tylko wtedy, gdy nic innego nie działa.

Iftah
źródło
idealne rozwiązanie do użytku w angularjs typeahead. Dzięki za poprowadzenie mnie we właściwym kierunku!
JuanTrev
1
Doskonałe rozwiązanie polegające na czekaniu na coś stworzonego przez Ajax przed umieszczeniem tam czegoś innego. Wielkie dzięki.
Countzero
@iftah Jak mam to uruchomić, jeśli selektor jest zmienną? Ponadto, jeśli jest to identyfikator lub selektor klasy, również się zmienia. Czasami zwracanych jest wiele elementów, gdy wybieram za pomocą klasy, i musiałbym znaleźć sposób na przekazanie indeksu do selektora, aby dowiedzieć się, który z nich. Jak bym to zrobił? Dzięki
Kragalon
@Kraglon to zupełnie inne pytanie i nie nadaje się do komentowania tej odpowiedzi. Proponuję zadać nowe pytanie, wyjaśnić, czego próbowałeś, w czym problem itp.
Iftah
8
Jeszcze jedna rzecz, o której należy wspomnieć, korzystając z podanego rozwiązania, powinieneś mieć ten fragment kodu w pętli for i ustawić maksymalny licznik ponownych prób, jeśli coś pójdzie nie tak, nie skończysz z pętlą nieskończoności :)
BJ
48

W zależności od tego, którą przeglądarkę chcesz obsługiwać, istnieje opcja MutationObserver .

EDYCJA: Wszystkie główne przeglądarki obsługują teraz MutationObserver .

Coś w tym stylu powinno załatwić sprawę:

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

Uwaga: sam nie testowałem tego kodu, ale taki jest ogólny pomysł.

Możesz łatwo rozszerzyć to, aby przeszukać tylko część DOM, która uległa zmianie. W tym celu użyj mutationsargumentu, jest to tablica MutationRecordobiektów.

cholera
źródło
2
Uwielbiałem ten. Dziękuję Ci.
insign
1
Ten wzorzec jest bardzo pomocny w wielu przypadkach, zwłaszcza jeśli przeciągasz JS na stronę i nie wiesz, czy ładowane są inne elementy.
Na imię
1
Najlepsza odpowiedź! Dzięki!
Antony Hatchkins
1
Utknąłem w starej przeglądarce (ff38) i to mnie uratowało.
jung rhew
1
To jest niesamowite! Chciałbym wiedzieć, że to istniało wcześniej.
Rob
39

Działa to tylko z nowoczesnymi przeglądarkami, ale wydaje mi się, że łatwiej jest po prostu użyć, thenwięc najpierw przetestuj, ale:

Kod

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

Lub za pomocą funkcji generatora

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

Stosowanie

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});
Jamie Hutber
źródło
Tam jest błąd. Korzystając z funkcji generatora, querySelector powinien być aktualizowany w każdej pętli:while (document.querySelector(selector) === null) {await rafAsync()}
haofly
31

Bardziej nowoczesne podejście do oczekiwania na elementy:

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

Zauważ, że ten kod musiałby być opakowany w funkcję asynchroniczną .


źródło
4
to jest całkiem fajne!
Dexter Bengil,
Co rtam jest ?
Daniel Möller
No dobrze, ale skąd to się bierze? Co to robi? Do czego wysyłasz setTimeout?
Daniel Möller
@ DanielMöller być może będziesz musiał przyjrzeć się Obietnicom, aby lepiej zrozumieć ten kod. Zasadniczo to, co robi tutaj kod, to ustawienie limitu czasu na 500 ms i czekanie na jego zakończenie przed uruchomieniem nowej iteracji pętli while. Sprytne rozwiązanie!
ClementParis016
8

Oto niewielka poprawa w stosunku do odpowiedzi Jamiego Hutbera

const checkElement = async selector => {

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

return document.querySelector(selector); };
wLc
źródło
8

Lepiej jest przekazywać requestAnimationFrameniż w setTimeout. to jest moje rozwiązanie w modułach es6 i przy użyciu Promises.

es6, moduły i obietnice:

// 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
źródło
Uncaught TypeError: Cannot read property 'then' of undefined
Jeff Puckett
Myślę, że przegapiłem powrót ... przed nową Obietnicą.
ncubica
1
To jest właściwe rozwiązanie, znacznie lepsze niż wszystkie okresowe sprawdzenia w oparciu o Timer.
András Szepesházi
4
W rzeczywistości to nie działa w obecnej formie. Jeśli $ someElement ma początkowo wartość null (tj. Jeszcze nie występuje w DOM), to przekazujesz tę wartość null (zamiast selektora CSS) do funkcji onElementReady i element nigdy nie zostanie rozwiązany. Zamiast tego przekaż selektor CSS jako tekst i spróbuj uzyskać odwołanie do elementu poprzez .querySelector w każdym przebiegu.
András Szepesházi
1
To działało świetnie w moim przypadku użycia i wydaje mi się właściwym rozwiązaniem. Dzięki
rdhaese
5

Oto rozwiązanie przy użyciu obserwabli.

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

Teraz możesz pisać

waitForElementToAppear(elementId).subscribe(el_ref => doSomethingWith(el_ref);
FrankL
źródło
4

Możesz sprawdzić, czy dom już istnieje, ustawiając limit czasu, aż zostanie już wyrenderowany w domenie.

var panelMainWrapper = document.getElementById('panelMainWrapper');
setTimeout(function waitPanelMainWrapper() {
    if (document.body.contains(panelMainWrapper)) {
        $("#panelMainWrapper").html(data).fadeIn("fast");
    } else {
        setTimeout(waitPanelMainWrapper, 10);
    }
}, 10);
Carmela
źródło
3

Jeśli chcesz uzyskać ogólne rozwiązanie wykorzystujące MutationObserver, możesz użyć tej funkcji

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

Źródło: https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
Przykład użycia

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

Uwaga: MutationObserver ma świetną obsługę przeglądarki; https://caniuse.com/#feat=mutationobserver

Zrobione ! :)

rdhainaut
źródło
2

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

Na wypadek, gdyby element nigdy się nie pojawił, więc nie sprawdzamy w nieskończoność.

heriberto perez
źródło