前端将 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 之前:
- 遍历所有 canvas
- 调用
canvas.toDataURL() - 缓存结果
- 在 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 时必须注意
- canvas 必须提前转为图片
- 不要嵌套 Paragraph
- ImageRun 必须直接在 Paragraph.children 中
- 不要试图 1:1 还原 HTML 结构
四、核心经验
Word 文档不是浏览器。
HTML 的语义嵌套不能直接映射到 docx。
当你开始:
- 把结构扁平化
- 图片独立成段
- 主动控制文档结构
问题就会变得清晰很多。