Markdown 转 PDF:我为什么选择浏览器原生打印方案?

3 min

把 Markdown 文档转成 PDF 格式看起来很简单,因为使用 html2pdf.js 配置非常方便,几十行代码就搞定了,转换成 PDF 也非常快,但是实际测试下来发现问题不少。 我在开发 Markdown 转 PDF 功能时折腾了好几天,反复修改了很多版代码,最后还是从 html2pdf.js 迁移到了浏览器原生打印方案。


Markdown 转 PDF 为什么选浏览器原生打印方案?

纯前端(无后端)转 PDF 基本就三个方案:

  1. 服务端渲染(Puppeteer/Playwright)- 静态站点不适用
  2. 客户端 JS 生成(html2pdf.js, jsPDF + html2canvas)- 位图输出
  3. 浏览器原生打印(window.print())- 矢量 PDF,分页由浏览器引擎处理

我最开始是使用了 html2pdf.js ,但是它的主要问题有:

  • 位图输出:PDF 其实是图片,选不了字也搜不了
  • 分页不稳:长代码块、嵌套列表容易被硬切(这是最大的问题)
  • 性能压力:长文档要渲染很多 Canvas,移动端很容易卡

后来改用浏览器原生打印,就完美解决了文字被硬切的问题,另外文字能选中能搜索,链接不丢,文件也更小,分页还更稳定。


核心架构:Overlay + iframe

直接 window.print() 会把整页 UI 都打印出来,我们使用 iframe 把内容分离出来,步骤如下:

  1. 点击“下载 PDF”
  2. 生成全屏 overlay 和工具栏
  3. 按模板生成打印样式
  4. 把带样式的 HTML 写入 iframe
  5. 用户点击打印,触发 iframe.contentWindow.print()

这样既能预览,又不会影响主页面样式。


CSS 分页关键规则

@media print {
  tr, pre, blockquote, img, .katex-display {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  h1, h2, h3, h4, h5, h6 {
    page-break-after: avoid;
    break-after: avoid;
  }
  table { page-break-inside: auto; }
  thead { display: table-header-group; }
  body {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

这些规则可以避免截断、重复表头、保留背景色。


打印对话框引导

建议在 UI 里直接提示用户这样设置:

  1. 目标:另存为 PDF
  2. 背景图形:勾选
  3. 页眉页脚:取消
  4. 纸张:A4 或 Letter
  5. 页边距:默认

总结

只要能接受弹出打印对话框,浏览器原生打印就是目前最稳、最省心、质量也最好的方案。 如果大家有更好的方案或相关的问题,欢迎留言讨论。


相关链接