使用 fetch 和 async/await 加载图像后在 canvas 上绘制图像时出现问题

Problem with drawing image on canvas after loading it with fetch and async/await

我尝试使用 fetch 和 async/await 加载图像并在 canvas 上显示它,但它不起作用。图片未在 canvas 上绘制。它正确加载到 HTML 中的 img 元素中。在 canvas 上,如果我将绘图代码包装在 setTimeout 中它可以工作,但我宁愿不这样做。

有没有什么方法可以在 canvas 上使用 await 和 fetch 加载图像并在没有 setTimeout 的情况下绘制图像?

代码如下:

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>

即使您使用的是内存中的 blob URL,它仍然需要由浏览器加载。

您在将 img 添加到 canvas 之前没有等待加载,所以它添加的只是一个空白。

注册一个 load 事件处理程序或使用较新的 HTMLImageElement.decode() 等待图像准备就绪,然后再将其添加到 canvas。

async function loadImage(url) {
  let response = await fetch(url);
  let blob = await response.blob();
  return URL.createObjectURL(blob);
}

const img = document.querySelector("img");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const url = "https://avatars.githubusercontent.com/u/92330684?s=120&v=4"

loadImage(url).then(async (tileURL) => {

  // Displaying image element in HTML works.
  img.src = tileURL;
  
  // wait for it to load
  await img.decode()
  
  ctx.drawImage(img, 0, 0);
});
canvas {
  border: 1px solid #000;
}
<canvas></canvas>
<img>

加载图像始终是异步的,无论来源如何*。您需要等待它加载完毕才能对其进行任何操作。

现在,不清楚你为什么在这里使用 fetch,你可以直接将图像的 .src 设置为 URL:

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>

然而,如果你有一个 Blob(或者实际上被迫使用 fetch),那么直接从这个 Blob 创建一个 ImageBitmap。这是生成和存储 CanvasImageSource 的最高效方法(将在 drawImage() 中使用)。
对于不支持此方法的旧浏览器,我通过 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 -->

*缓存的图像可能会在下一次调用 drawImage() 之前准备就绪,但永远不要这样假设。