阅读视图

发现新文章,点击刷新页面。

前端将 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 中存在:

<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 需要的是:

Uint8Array(二进制图片数据)

而 canvas:

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

尤其是:

element.cloneNode(true)

克隆出来的 canvas:

不包含绘制内容

正确做法

必须在克隆 HTML 之前:

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

核心原则:

canvas 先转图片,再克隆 DOM。


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

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

现象:

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

打印结构后发现:

Paragraph
  └─ Paragraph
       └─ ImageRun

也就是说:

ImageRun 外面包了两层 Paragraph。

问题本质

在 docx 结构中:

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

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

Word 会忽略或报结构错误。

正确结构

new Paragraph({
  children: [
    new ImageRun(...)
  ]
})

而不是:

new Paragraph({
  children: [
    new Paragraph({
      children: [
        new ImageRun(...)
      ]
    })
  ]
})

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

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

<div>
  <p>
    文字
    <img />
  </p>
</div>

但 docx 并不是 DOM 树结构。

docx 的正确模型更像是:

Section
 ├─ Paragraph
 ├─ Paragraph
 ├─ Paragraph

是一个扁平结构。

因此正确做法是:

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

例如:

<p>hello <img /> world</p>

应转换为:

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。

当你开始:

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

问题就会变得清晰很多。

❌