You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何需两次点击DRAW按钮才能获取Canvas及base64格式图片

问题根因

需要点击两次按钮才生效是两个逻辑错误导致的:

  • crossOrigin属性必须在图片发起网络请求前设置才会生效。第一次点击按钮时,页面上的两张图片早就加载完成了,这时候再追加crossOrigin="anonymous"不会触发带跨域请求头的资源重新拉取,第一次绘制用的还是本地缓存里没有CORS许可头的资源,要么绘制失败要么画布被污染无法导出base64。第一次点击触发属性修改后浏览器才会重新请求带跨域头的图片资源,第二次点击时资源已经加载完成,所以才能正常展示。
  • 代码里的Promise.all逻辑没有等待图片加载完成,只是遍历了序号就直接进入then执行绘制、导出操作,执行drawImage时图片资源还没加载完毕,画布自然是空白的。
  • 移除crossOrigin属性后抛出的画布污染错误是浏览器默认的安全限制:没有返回合法CORS许可头的跨域图片绘制到画布后,画布内容会被标记为不可读,调用toDataURL就会触发这个报错,防止跨域数据泄露。
修复方案

不要复用页面上已经加载完成的img元素做跨域绘制,点击按钮时新建带跨域属性的图片实例,等所有图片加载完成后再执行绘制和导出逻辑,修复后的完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    button {
      padding: 10px 50px;
      cursor: pointer;
    }
    .custom {
      width: 200px;
      height: 200px;
    }
    .canvas-container {
      border: 5px solid #000;
      margin-bottom: 10px;
      margin-top: 10px;
    }
  </style>
  <script>
    const draw = () => {
      const imgSrcList = [
        "https://cdn.pixabay.com/photo/2016/02/05/19/51/stained-glass-1181864_960_720.jpg",
        "https://cdn.pixabay.com/photo/2018/10/08/13/05/hindu-3732713_960_720.jpg"
      ];
      const canvas = document.querySelector("#screenShotCanvas");
      const context = canvas.getContext("2d");
      const resultImg = document.querySelector("#cnimg");
      const singleImgWidth = 700;
      const singleImgHeight = 450;
      canvas.width = imgSrcList.length * singleImgWidth;
      canvas.height = singleImgHeight;

      // 等待所有跨域图片加载完成再绘制
      const loadPromiseList = imgSrcList.map((src, index) => {
        return new Promise((resolve, reject) => {
          const img = new Image();
          // 必须在赋值src前设置crossOrigin
          img.crossOrigin = "anonymous";
          img.onload = () => {
            context.drawImage(img, index * singleImgWidth, 0, singleImgWidth, singleImgHeight);
            resolve();
          };
          img.onerror = reject;
          // 拼接时间戳绕过本地无CORS头的缓存资源
          img.src = `${src}?t=${Date.now()}`;
        })
      });

      Promise.all(loadPromiseList)
        .then(() => {
          const canvasBase64 = canvas.toDataURL();
          resultImg.src = canvasBase64;
        })
        .catch((error) => {
          console.error("图片加载或绘制失败:", error);
        });
    };
  </script>
</head>
<body>
  <button onclick="draw()">DRAW</button>
  <div class="canvas-container">
    <h2>点击DRAW按钮后Canvas拼接图会显示在这里</h2>
    <canvas id="screenShotCanvas"></canvas>
  </div>
  <div>
    <img
      class="custom"
      src="https://cdn.pixabay.com/photo/2016/02/05/19/51/stained-glass-1181864_960_720.jpg"
      id="img1"
    />
    <img
      class="custom"
      src="https://cdn.pixabay.com/photo/2018/10/08/13/05/hindu-3732713_960_720.jpg"
      id="img2"
    />
  </div>
  <div>
    <h2>Base64转换后的图片会显示在这里</h2>
    <img src="" id="cnimg" alt="base64" />
  </div>
</body>
</html>
关键注意点
  • crossOrigin属性必须在给img赋值src之前设置,否则属性不会生效,浏览器不会携带跨域请求头。
  • 必须等所有图片触发onload事件后再执行drawImage操作,否则绘制时资源未加载完成,画布会是空白状态。
  • 给图片地址拼接时间戳参数,是为了绕过浏览器本地缓存的不带CORS响应头的旧资源,避免缓存导致的跨域报错。

内容的提问来源于stack exchange,提问作者Abhishek kamal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 00:39:02