Javascript-forum
Warten bis image geladen ist - Druckversion

+- Javascript-forum (https://javascript-forum.de)
+-- Forum: Entwicklung (https://javascript-forum.de/forumdisplay.php?fid=4)
+--- Forum: Javascript (https://javascript-forum.de/forumdisplay.php?fid=6)
+--- Thema: Warten bis image geladen ist (/showthread.php?tid=2729)



Warten bis image geladen ist - mike64 - 17.01.2024

Hallo

es sollte einfach sein, aber es ist wohl ein bekanntes Problem mit dem asynchronen Laden eines images

Mein Problem ist, daß ich ähnlich einer VCL Objekte erzeuge, die in Canvas grafisch dargestellt werden, ihrerseits buttons enthalten usw.
Das klappt auch alles ganz gut, solange die darzustellenden Bilder geladen sind.
Lege ich ein Objekt an, wird das Bild geladen und ich benötige bereits in der nächsten Zeile die Bilddaten, die aber noch nicht zur Verfügung stehen.

Weitere Objekte hängen dann von diesem Objekt ab, ich kann es also nicht asynchron programmieren

Der Versuch auf das Bild zu warten scheitert an der Endlosschleife, hier mal mit ausgeklammertem Timeout.

Die Bedingung wird nie erfüllt, auch nicht wenn ich auf image.height!=0 teste.

Ich vermute mal, daß in der Schleife zumindestens mal so etwas wie processmessages stehen muß, damit andere Arbeiten weiter gehen?

Wer weiß Rat?

Code:
function loadImg (img_url)
{
const image = new Image();
imgLoad=false;
image.onload = function () {imgLoad=true;};
image.src = img_url;
var imgTO=false;

//var to=setTimeout(() => {
//  imgTO=true;
//}, 1000);

while ((!imgLoad)) { }; // wait && (!imgTO)
//clearTimeout(to);

//if (imgTO) { return null; } else { return image; }
return image;
}



RE: Warten bis image geladen ist - Sempervivum - 17.01.2024

Hallo,
Zitat:Weitere Objekte hängen dann von diesem Objekt ab, ich kann es also nicht asynchron programmieren

"geht nicht gibt's nicht", aber von Anfang an:

Du hast vollkommen Recht, mit diesem Problem ist jeder konfrontiert, der Bilder lädt und bei der weiteren Verarbeitung darauf an gewiesen ist, dass sie fertig geladen sind.

Das Verfahren mit der Schleife ist machbar, es gibt da eine Eigenschaft, die angibt ob das Bild vollständig geladen ist:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/complete
Aber auf der anderen Seite nicht sehr elegant, es gibt bessere.

Einfachste Möglichkeit: Die weitere Verarbeitung vom load-Event abhängig machen, etwa so:
Code:
const image = new Image();
image.onload = event => {
    // Alle weitere Verarbeitung,
    // die auf dem vollständig geladenen Bild aufbaut,
    // hier drin
};
image.src = img_url;

Großer Nachteil dabei: Das Ganze wird unübersichtlich verschachtelt, wenn man auf mehrere Bilder warten muss.

Alternative für mehrere Bilder: Eine Funktion definieren, die bei jedem load aufgerufen wird und einen Zähler erhöht, der die geladenen Bilder zählt. Hat dieser die Anzahl der zu ladenden Bilder erreicht, wird die weitere Verarbeitung gestartet.

Zitat:Lege ich ein Objekt an, wird das Bild geladen und ich benötige bereits in der nächsten Zeile die Bilddaten, die aber noch nicht zur Verfügung stehen.

Auch das ist machbar, wenn Du das Verfahren async - await anwendest. Setzt allerdings voraus, dass sich alles in einer async-Funktion abspielt und wird z. B. hier beschrieben:
https://www.fabiofranchino.com/log/load-an-image-with-javascript-using-await/

Ich kenne aber keine Möglichkeit, die nicht von einer kapselnden Funktion abhängig ist.

Wenn man mal von dem veralteten XMLHttpRequest absieht, wo man auf synchrone Verarbeitung umschalten kann. Davon raten aber alle verlässlichen Quellen ab.


RE: Warten bis image geladen ist - mike64 - 19.01.2024

Danke für die Ausführungen

leider komme ich nicht weiter

1) Die Abfrage per Schleife scheitert daran, daß ich mich immer in eine Endlosschleife begebe, was den Browser zum Absturz bringt. Das Ereignis tritt nie ein. Ich vermute, der Interpreter führt da keine weiteren Arbeiten aus, sodaß die Variable irgendwann mal geändert wird und die Schleife verlassen wird.
Weder mit complete, noch mit flags.

2) Der Versuch mit await scheitert auch.
Ich habe es so verstanden, daß await hier wirklich wartet, muß aber feststellen, daß mein Hauptprogramm weiter läuft. Gemäß dem Beispiel habe ich meine Prozeduren mit await geschrieben und sehe in der Konsole zahlreiche Bildanforderungen und irgendwann kommt dann mal die ein oder andere "Geladen" Meldung. Somit wartet das Programm hier nicht sondern arbeitet weiter. Nur die Async Funktion wartet. Dann wäre das nur eine andere Programmierung bei sonst gleicher Problematik. Ich benötige ein echtes Stop und bekomme es nicht hin.


Code:
function loadImage (path)
{
  console.log ('bild'+path);
    return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'Anonymous'; // to avoid CORS if used with Canvas
    img.src = path;
    img.onload = () => {
      resolve(img)
    }
    img.onerror = e => {
      reject(e)
    }
  })
}

async function loadImg (img_url)
{
  console.log ('Image anfordern');
  img = await loadImage(img_url);
  console.log ('Image geladen');
  console.log (img);
  return img;
}



RE: Warten bis image geladen ist - Sempervivum - 19.01.2024

Ich schrieb ja schon:
Zitat:Ich kenne aber keine Möglichkeit, die nicht von einer kapselnden Funktion abhängig ist.
Bei asynchronen Vorgängen kommst Du nicht daran vorbei, die Bearbeitung in eine Funktion hinein zu schreiben. Ich sehe aber keinen Grund, warum das nicht machbar sein sollte.
async-await hat nur den Vorteil, dass Du keine Verschachtelungen bekommst.

Code:
async function loadImg (img_url)
{
  console.log ('Image anfordern');
  img = await loadImage(img_url);
  console.log ('Image geladen');
  console.log (img);
  // Alles was das img braucht, hier rein
}



RE: Warten bis image geladen ist - mike64 - 19.01.2024

Ok, aber jetzt setze ich noch einen drauf:

Ich habe mal eine Warteschleife in der aufrufenden Prozedur probiert

zuvor werden rund 30 Icons mit Loadimage geladen

Der Zähler erhöht sich erwartungsgemäß mit jedem Aufruf des Constructors, wird aber niemals niedriger.

Dagegen wird der Zähler niedriger sobald Arbeiten erledigt sind. Anscheinend, sobald der Browser Freizeit hat. 
Somit habe ich mein Problem mit der Endlosschleife wenn ich auf imgreq==0 teste.

Das würde bedeuten, daß ich die Anwendung ähnlich einer Interruptroutine komplett ereignisgesteuert programmieren muß.
Also eine Befehlswarteschlange die von zahlrreichen Loadimg getriggert ist (das kann es doch nicht sein)

Jetzt könnte ich natürlich erst das image laden und den Device constructor mit onload aufrufen, würde für die einzelnen Objekte funktionieren

Dann hänge ich spätestens bei drawcanvas wieder in der Situation, daß ich prüfen muß ob alles geladen ist und da ist sie wieder, meine nie erfüllte Endlosschleife.


Aufrufende Prozedur (Erstellung eines Objekts)
Code:
class TDevice
{
   image='';
   img=0;
   buttons=[];
   name='';

constructor (image)
  {
    this.image=image;
    if (image!='' )
    { this.img=loadImg (image);
      var i =0;
      do {
           if (i % 100000==0)   {console.log ('Waiting'+i+' '+imgreq);}
            i = i + 1;

      } while (i < 100000000 );
      this.buttons[0]={t:'M',img:img_menu,x:this.img.width-20,y:5,w:img_menu.width,h:img_menu.height};
    }
    else {this.img=null;}

Loadimage mit Zähler

Code:
function loadImg (img_url)
{
const image = new Image();
imgreq++;
console.log('requested :' +imgreq);
image.onload = function () {imgreq--; console.log ('pending :'+imgreq);};
image.src = img_url;
return image;
}



RE: Warten bis image geladen ist - Sempervivum - 19.01.2024

Nein, keine Endlosschleife. Ich meinte das so:
Code:
        const imgUrls = ['images/dia0.jpg', 'images/dia1.jpg', 'images/dia2.jpg'];
        let counter = imgUrls.length;
        function checkReady() {
            counter--;
            if (counter == 0) {
                // Jetzt sind alle Bilder geladen und die Bearbeitung,
                // die das voraus setzt, kann beginnen
            }
        }
        imgUrls.forEach(url => {
            const img = new Image();
            img.onload = event => {
                // Hier kannst Du das Objekt für das Bild anlegen

                // Prüfen ob alle Bilder geladen sind:
                checkReady();
            }
            img.src = url;
        });
Müsste man noch für den Fehlerfall erweitern, falls ein Bild nicht geladen werden kann.

Und mit async-await:
Code:
        async function loadImages() {
            const loadImage = path => {
                return new Promise((resolve, reject) => {
                    const img = new Image()
                    img.crossOrigin = 'Anonymous' // to avoid CORS if used with Canvas
                    img.src = path
                    img.onload = () => {
                        resolve(img)
                    }
                    img.onerror = e => {
                        reject(e)
                    }
                });
            }
            const img1 = await loadImage('images/dia0.jpg');
            const img2 = await loadImage('images/dia1.jpg');
            const img3 = await loadImage('images/dia2.jpg');
            // An dieser Stelle sind alle Bilder geladen
            console.log('all images ready')
        }
        loadImages();



RE: Warten bis image geladen ist - mike64 - 19.01.2024

Ok vielen Dank.

die Verkettung der awaits kam mit heute Mittag auch bei einem Spaziergang mit dem Hund im Wald. Manchmal soll man mit Anstand auf die Dinge schauen.

Für die Nachwelt halte ich nochmal den gesamten Code fest, wie es funktioniert:

Am Ende der geladenen Seite rufe ich initCanvas() auf. Diese asynchrone Funktion übernimmt dann den kompletten Aufbau. drawCanvas() komt dann zum Schluß
Wichtig ist, daß alles in dieser async Funktion steht, denn nur so wird es nacheinander abgearbeitet.

Nimmt der Benutzer Änderungen an den darzustellenden Objekten vor muß genauso verfahren werden. In einer async Funktion nacheinander die hinzugekommenen Bilder laden und die Objekte dazu anlegen.

Eigentlich ganz einfach, wenn man weiß wie es geht Wink

Code:
function loadImage (path)
{
    return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'Anonymous'; // to avoid CORS if used with Canvas
    img.src = path;
    img.onload = () => {
      resolve(img)
    }
    img.onerror = e => {
      reject(e)
    }
  })
}
async function initCanvas ()
{
  img_hjunc=await loadImage('images/KP_HORZ_Abgang.png');
  img_vjunc=await loadImage('images/KP_VERT_Abgang.png');
  // ...
  //  weitere images laden
  // ...
  // nun kommt der Aufbau meiner darzustellenden Objekte
  // erst image laden, ann den constructor aufrufen und das image übergeben
  img=await loadImage ('images/KP_basegrid.png');
  node=KTree.addnode (new TTreenode(null,new TBase(img))); 
  img=await loadImage ('images/KP_Zaehler.png');
  node=KTree.addnode (new TTreenode(node,new TCTR(img))); 
  // ...
  // weitere Objekte anlegen
  // ...

  // jetzt ist alles geladen nun ausgeben
  drawCanvas();  // eigene Funktion, die alle Objekte ausgibt
}




initCanvas();