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

HTML导出Word文档首次无图片、后续导出正常的问题排查

HTML导出Word文档首次无图片、后续导出正常的问题排查

嘿,我之前也碰到过几乎一模一样的坑!咱们来一步步拆解你的问题,看看为啥第一次导出没图片,后面又正常了。

问题根源分析

先看你处理图片的这段代码:

// 你写的图片处理逻辑里有这么一行
img[i].src = img[i].src;

这行代码是多余且有害的!它会触发图片的强制重新加载——第一次调用函数时,图片刚触发重新加载还没完成,你就用canvas.drawImage()去绘制,这时候拿到的是未加载完成的图片资源,生成的DataURL自然是无效的,所以第一次导出的Word里看不到图片;第二次调用时,图片已经被浏览器缓存了,重新加载瞬间完成,canvas能正确绘制图片,所以导出就正常了。

另外还有个隐藏风险:如果页面里的图片是动态加载的(比如懒加载),就算去掉这行代码,第一次调用时图片可能还没加载完成,同样会导致canvas绘制失败。

解决方案

咱们分两步修改:

1. 删掉多余的图片重新加载代码

直接把img[i].src = img[i].src;这行删掉,别让它乱触发图片加载了。

2. 确保图片完全加载后再处理

给图片加个加载状态检查,保证用canvas绘制时,图片已经100%加载完成。我们可以用Promise来批量处理图片加载:

首先,加一个辅助函数来判断图片是否加载完成:

// 辅助函数:等待图片加载完成
function waitForImageLoad(imgElement) {
  return new Promise((resolve, reject) => {
    // 如果图片已经加载完成,直接返回
    if (imgElement.complete && imgElement.naturalHeight !== 0) {
      resolve(imgElement);
    } else {
      // 否则监听load/error事件
      imgElement.addEventListener('load', () => resolve(imgElement));
      imgElement.addEventListener('error', (err) => reject(`图片加载失败: ${err}`));
    }
  });
}

然后修改你的图片处理逻辑,把函数改成异步的,等待所有图片加载完成后再处理:

// 把原函数改成async函数
export async function exportToDoc(element, filename = "") {
  let html =
    "<html xmlns:o='urn:schemas-microsoft-com:office:office'" +
    " xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta><title>Export HTML To Doc</title></head><body>";
  let footer = "</body></html>";
  html = html + document.getElementById(element).innerHTML + footer;

  var statiC = {
    mhtml: {
      top:
      /* eslint-disable */
        "Mime-Version: 1.0\nContent-Base: " +
        location.href +
        '\nContent-Type: Multipart/related; boundary="NEXT.ITEM-BOUNDARY";type="text/html"\n\n--NEXT.ITEM-BOUNDARY\nContent-Type: text/html; charset="utf-8"\nContent-Location: ' +
        location.href +
        "\n\n<!DOCTYPE html>\n<html>\n_html_</html>",
      head: '<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<style>\n_styles_\n</style>\n</head>\n',
      body: "<body>_body_</body>",
    },
  };

  var css =
    "<style>" +
    "img {width:auto; height: 800px, margin-right: 100px}" +
    "</style>";

  // --- 图片处理部分修改开始 ---
  var options = { maxWidth: 500 };
  const targetElement = document.getElementById(element);
  const imgElements = Array.from(targetElement.querySelectorAll("img"));

  try {
    // 等待所有图片加载完成
    const loadedImages = await Promise.all(imgElements.map(waitForImageLoad));
    var images = [];

    for (var i = 0; i < loadedImages.length; i++) {
      const img = loadedImages[i];
      // 计算图片尺寸
      var w = Math.min(img.width, options.maxWidth);
      var h = img.height * (w / img.width);
      // 创建canvas绘制
      var canvas = document.createElement("CANVAS");
      canvas.width = w;
      canvas.height = h;
      var context = canvas.getContext("2d");
      context.drawImage(img, 0, 0, w, h);
      // 生成DataURL
      var uri = canvas.toDataURL("image/png");
      // 这里已经删掉了多余的img.src赋值
      img.width = w;
      img.height = h;
      // 保存图片元数据
      images[i] = {
        type: uri.substring(uri.indexOf(":") + 1, uri.indexOf(";")),
        encoding: uri.substring(uri.indexOf(";") + 1, uri.indexOf(",")),
        location: img.src,
        data: uri.substring(uri.indexOf(",") + 1),
      };
    }
  } catch (err) {
    console.error("图片加载出错:", err);
    alert("部分图片加载失败,导出可能不完整");
    return;
  }
  // --- 图片处理部分修改结束 ---

  // 下面的代码保持不变
  var imgMetaData = "\n";
  for (var i = 0; i < images.length; i++) {
    imgMetaData += "--NEXT.ITEM-BOUNDARY\n";
    imgMetaData += "Content-Location: " + images[i].location + "\n";
    imgMetaData += "Content-Type: " + images[i].type + "\n";
    imgMetaData += "Content-Transfer-Encoding: " + images[i].encoding + "\n\n";
    imgMetaData += images[i].data + "\n\n";
  }
  imgMetaData += "--NEXT.ITEM-BOUNDARY--";

  const output = statiC.mhtml.top.replace("_html_", statiC.mhtml.head.replace("_styles_", css) + html) + imgMetaData;
  const url = "data:application/vnd.ms-word;charset=utf-8," + encodeURIComponent(output);

  filename = filename ? filename + ".doc" : "document.doc";

  let downloadLink = document.createElement("a");
  document.body.appendChild(downloadLink);
  downloadLink.href = url;
  downloadLink.download = filename;
  downloadLink.click();
  document.body.removeChild(downloadLink);
}

3. 调用异步函数的注意事项

因为我们把函数改成了async,调用的时候要加await,或者用.then():

// 调用方式1:在async函数里用await
async function handleExport() {
  await exportToDoc("your-element-id", "导出文档");
}

// 调用方式2:用.then()
exportToDoc("your-element-id", "导出文档").catch(err => console.error(err));

验证效果

修改完之后,第一次调用导出函数时,会先等待所有图片加载完成,再生成canvas和DataURL,这样第一次导出的Word里就能正常显示图片了,后面的导出也会保持正常。

备注:内容来源于stack exchange,提问作者JohnJones357

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 03:35:29