前端将 HTML 转成 Word 文档的踩坑记录(从 html-docx-js到html-to-docx 到 docx)

前端将 HTML 转成 Word 文档的踩坑记录(从 html-docx-js 到 docx)

在项目中,我需要实现一个功能:

👉 将页面渲染出来的 HTML 内容导出为 Word 文档(.docx)

看起来很简单,但真正落地时踩了不少坑。

这篇文章记录一下从插件选择到最终解决方案的全过程。


一、插件选型对比

1️⃣ html-docx-js

最早尝试的是 html-docx-js

优点:

  • 使用简单
  • 直接将 HTML 字符串转换成 Word

但是很快遇到了问题:

❗ 多层级有序列表在 WPS 中显示异常

当 HTML 中存在:

css 复制代码
<ol>
  <li>一级</li>
  <li>
    二级
    <ol>
      <li>子级</li>
    </ol>
  </li>
</ol>

Microsoft Word 中显示正常,

但在 WPS 中会出现:

  • 序号错乱
  • 层级缩进异常
  • 列表结构被打乱

也就是说:

html-docx-js 在生成的 docx 结构中,列表兼容性并不稳定。

对于需要兼容 WPS 的场景来说,这是不可接受的。


2️⃣ html-to-docx / htmltodoc

后来尝试 html-to-docx 这一类库。

问题也很明显:

❗ 不支持 canvas 图片

如果页面中有:

  • canvas 图表
  • 图形绘制
  • Echarts
  • GPT 可视化图表

导出后:

图片为空白

原因是:

  • 这些库只识别 <img>
  • 不会处理 <canvas> 的内容
  • 不会主动把 canvas 转成图片

在图表场景下,这几乎无法使用。


3️⃣ 最终选择:docx

最后选择了 docx(dolanmiu/docx)。

原因:

  • 底层生成真实 docx 结构
  • 可控性强
  • 可自定义 ImageRun / Paragraph
  • 兼容性更好

但同时:

自己要负责 HTML → docx 的映射逻辑。

这也是后面踩坑的开始。


二、使用 docx 时踩到的坑


坑 1:canvas 图片第一次导出是空白

现象:

  • 页面中 canvas 渲染正常
  • 第一次导出 Word,图片是空白
  • 第二次导出却正常

原因

docx 需要的是:

javascript 复制代码
Uint8Array(二进制图片数据)

而 canvas:

  • 是绘图上下文
  • 不是图片资源
  • 如果在 clone 之后再去读取,很可能上下文已经丢失

尤其是:

arduino 复制代码
element.cloneNode(true)

克隆出来的 canvas:

不包含绘制内容

正确做法

必须在克隆 HTML 之前:

  1. 遍历所有 canvas
  2. 调用 canvas.toDataURL()
  3. 缓存结果
  4. 在 clone 后替换成 <img src="dataURL">

核心原则:

canvas 先转图片,再克隆 DOM。


坑 2:ImageRun 被嵌套在 Paragraph 中,图片直接消失

这是最隐蔽、最坑的一个问题。

现象:

  • 图片数据正确
  • 不跨域
  • 二进制正常
  • 但导出 Word 后图片消失
  • 有时 Office Word 还会提示文件有问题

打印结构后发现:

markdown 复制代码
Paragraph
  └─ Paragraph
       └─ ImageRun

也就是说:

ImageRun 外面包了两层 Paragraph。

问题本质

在 docx 结构中:

  • Paragraph 是块级元素
  • Paragraph 不能嵌套 Paragraph
  • ImageRun 必须直接存在于 Paragraph.children 中

非法结构虽然可以被创建,但:

Word 会忽略或报结构错误。

正确结构

arduino 复制代码
new Paragraph({
  children: [
    new ImageRun(...)
  ]
})

而不是:

css 复制代码
new Paragraph({
  children: [
    new Paragraph({
      children: [
        new ImageRun(...)
      ]
    })
  ]
})

坑 3:HTML 的结构 ≠ docx 的结构

在 Markdown 渲染后,HTML 往往是这样:

css 复制代码
<div>
  <p>
    文字
    <img />
  </p>
</div>

但 docx 并不是 DOM 树结构。

docx 的正确模型更像是:

css 复制代码
Section
 ├─ Paragraph
 ├─ Paragraph
 ├─ Paragraph

是一个扁平结构。

因此正确做法是:

  • 文字 → 一个 Paragraph
  • 图片 → 一个 Paragraph
  • 保持顺序
  • 不强行还原 HTML 嵌套

例如:

css 复制代码
<p>hello <img /> world</p>

应转换为:

scss 复制代码
Paragraph("hello")
Paragraph(Image)
Paragraph("world")

而不是试图在一个 Paragraph 里混排。


三、最终总结

在前端做 HTML → Word 导出时,需要注意:

插件层面

  • html-docx-js:WPS 兼容性问题
  • html-to-docx:不支持 canvas
  • docx:可控但需要自己处理结构

使用 docx 时必须注意

  1. canvas 必须提前转为图片
  2. 不要嵌套 Paragraph
  3. ImageRun 必须直接在 Paragraph.children 中
  4. 不要试图 1:1 还原 HTML 结构

四、核心经验

Word 文档不是浏览器。

HTML 的语义嵌套不能直接映射到 docx。

当你开始:

  • 把结构扁平化
  • 图片独立成段
  • 主动控制文档结构

问题就会变得清晰很多。

相关推荐
猫猫不是喵喵.几秒前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~1 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华2 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni5 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学6 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc886 小时前
微软EDGE浏览器功能学习
前端·学习·edge