I tried to load image using fetch and async/await and display it on canvas, but it doesn't work. The image isn't drawn on canvas. It loads correctly into img element in HTML. On canvas if I wrap the drawing code in setTimeout it works, but I would prefer to not to do this.
Is there some way to load the image using await and fetch and draw it on canvas without setTimeout?
Here is the code:
async function loadImage(url) {
let response = await fetch(url);
let blob = await response.blob();
return URL.createObjectURL(blob);
}
let canvas = document.body.querySelector("canvas");
let ctx = canvas.getContext("2d");
let tileURL = loadImage("https://avatars.githubusercontent.com/u/92330684?s=120&v=4").then((tileURL) => {
// Displaying image element in HTML works.
let img = document.body.querySelector("img");
img.src = tileURL;
// Displaying the image immediately in canvas doesn't work.
ctx.drawImage(img, 0, 0);
// But it works if I add some delay.
setTimeout(() => {
ctx.drawImage(img, 100, 0);
}, 3000); // 3 second delay.
});
canvas {
border: 1px solid #000;
}
<canvas></canvas>
<img>
Loading an image is always asynchronous, whatever the source*. You need to wait for it has loaded before being able to do anything with it.
Now, it's unclear why you are using fetch
here, you can very well just set the image's .src
to the URL directly:
const canvas = document.body.querySelector("canvas");
const ctx = canvas.getContext("2d");
// Displaying image element in HTML works.
const img = document.body.querySelector("img");
img.crossOrigin = "anonymous"; // allow read-back
img.src = "https://avatars.githubusercontent.com/u/92330684?s=120&v=4";
img.onload = (evt) => // or await img.decode() if in async
ctx.drawImage(img, 0, 0);
canvas {
border: 1px solid #000;
}
<canvas></canvas>
<img>
However, if you have a Blob (or are actually forced to use fetch
), then create an ImageBitmap from this Blob directly. This is the most performant way to produce and store a CanvasImageSource (to be used in drawImage()
).
For older browsers that didn't support this method, I got you covered through this polyfill of mine.
async function loadImage(url) {
const response = await fetch(url);
const blob = response.ok && await response.blob();
return createImageBitmap(blob);
}
const canvas = document.body.querySelector("canvas");
const ctx = canvas.getContext("2d");
loadImage("https://avatars.githubusercontent.com/u/92330684?s=120&v=4").then((bitmap) => {
ctx.drawImage(bitmap, 0, 0);
});
canvas {
border: 1px solid #000;
}
<!-- createImageBitmap polyfill for old browsers --> <script src="https://cdn.jsdelivr.net/gh/Kaiido/createImageBitmap/dist/createImageBitmap.js"></script>
<canvas></canvas>
<!-- HTMLImage is not needed -->
*It may happen that cached images are ready before the next call to drawImage()
, but one should never assume so.