背景
我用 Vue 3 开发了一个在线简历编辑器,支持手动插入分页符,并导出为 PDF。PDF 生成使用的是 dompdf.js v2.0.0,一个基于 WASM 的纯前端 DOM 转 PDF 引擎。
功能看起来很简单:用户在界面上插入分页符,导出 PDF 时在对应位置分页。但实际开发中踩了不少坑。
环境
- Vue 3 + Composition API
- dompdf.js v2.0.0(WASM 版本)
- A4 纸张:96dpi 下宽度 794px,高度 1123px
第一个坑:dompdf.js 的分页机制
dompdf.js 的分页依赖两个关键 HTML 属性(注意:是 HTML 属性,不是 CSS 类名):
| 属性 | 作用 |
|---|---|
divisionDisable |
添加到不想被分页切断的元素上,跨页时整个块移到下一页 |
pageBreak |
添加到希望在其前面强制分页的元素上 |
dompdf.js 内部通过 el.hasAttribute('divisionDisable') 和 root.querySelectorAll('[pageBreak], [divisionDisable]') 来检测这些属性。
踩坑点 :我一开始把 divisionDisable 当成了 dompdf 的配置选项,写成了:
javascript
// ❌ 错误:这不是配置选项
const options = {
divisionDisable: '.page-break-inside-avoid, .header-section'
}
实际上 divisionDisable 必须作为 HTML 属性写在 DOM 元素上:
html
<!-- ✅ 正确:作为 HTML 属性 -->
<div class="section" divisionDisable>...</div>
第二个坑:强制分页的正确配置
dompdf.js 支持通过配置项指定在哪些元素前强制分页:
javascript
const options = {
pagination: true, // 必须启用
pageBreak: {
before: '[data-page-break="true"]' // CSS 选择器
}
}
对应的 HTML:
html
<div pageBreak data-page-break="true"></div>
第三个坑:v-show 和 v-if 会破坏分页
初始实现中,分页标记使用了 v-show="isEditing" 来控制可见性:
html
<div v-show="isEditing" class="page-break-marker">✂️ 分页符</div>
问题 :v-show="false" 会给元素添加 display: none,dompdf.js 会直接忽略 display: none 的元素,导致分页失效。
解决方案:改用 CSS 类控制可见性,确保元素始终在 DOM 中:
html
<div class="page-break-marker" :class="{ 'editing-visible': isEditing }">
✂️ 分页符(此处将在 PDF 中分页)
</div>
css
.page-break-marker {
height: 0;
overflow: hidden;
}
.page-break-marker.editing-visible {
height: auto;
overflow: visible;
}
第四个坑(最坑):CSS 隐藏对 dompdf.js 无效
导出 PDF 时,需要隐藏分页标记的文本。我尝试了:
css
.resume-paper.exporting .page-break-marker {
visibility: hidden !important;
height: 0 !important;
overflow: hidden !important;
}
结果:在浏览器中确实隐藏了,但导出的 PDF 中文本依然显示!
原因 :dompdf.js 不完全支持 CSS 的 visibility: hidden 和 height: 0 + overflow: hidden 组合。它会忽略这些样式,直接渲染元素内容。
最终解决方案
在调用 dompdf 之前,直接清除分页标记的内部内容,保留外层 div 和属性;生成 PDF 后再恢复:
javascript
const exportPDF = async () => {
let savedContents = []
try {
const paperEl = resumeRef.value.querySelector('.resume-paper')
paperEl.classList.add('exporting')
// 清除分页标记的内部内容(保留外层div和pageBreak属性供dompdf识别)
const markers = paperEl.querySelectorAll('.page-break-marker')
markers.forEach((marker, i) => {
savedContents[i] = marker.innerHTML
marker.innerHTML = ''
})
// 生成 PDF
const blob = await dompdf(paperEl, options)
// ... 下载逻辑
} finally {
// 恢复分页标记内容
const paperEl = resumeRef.value?.querySelector('.resume-paper')
if (paperEl) {
const markers = paperEl.querySelectorAll('.page-break-marker')
markers.forEach((marker, i) => {
if (savedContents[i] !== undefined) {
marker.innerHTML = savedContents[i]
}
})
paperEl.classList.remove('exporting')
}
}
}
完整配置示例
javascript
const options = {
format: 'a4',
pagination: true,
compress: true,
useCORS: true,
// 强制分页:在带有 data-page-break="true" 的元素前分页
pageBreak: {
before: '[data-page-break="true"]'
},
// 中文字体配置
fontConfig: {
fontFamily: 'SourceHanSansSC-Regular',
fontBytes: new Uint8Array(fontBuffer),
fontStyle: 'normal',
fontWeight: 400
},
// 页眉页脚配置
pageConfig: {
header: { content: '', height: 20 },
footer: { content: '', height: 0 }
}
}
总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 分页不生效 | divisionDisable 写成了配置项 |
改为 HTML 属性 |
| v-show 导致分页失效 | display: none 让 dompdf 忽略元素 |
改用 CSS 类控制可见性 |
| 强制分页不生效 | 没有正确配置 pageBreak.before |
添加选择器配置 |
| 分页文本在 PDF 中显示 | dompdf 忽略 CSS 隐藏样式 | 导出前清除 innerHTML,导出后恢复 |
核心原则:dompdf.js 对 CSS 的支持有限,不要依赖 CSS 来控制元素的显示/隐藏。需要隐藏元素内容时,直接操作 DOM 是最可靠的方式。