将 DOM 打印为 PDF 的实现方案¶
通用前端技巧,不依赖任何框架。将指定 DOM 区域克隆后在新窗口中打印,支持完整 CSS 样式,可直接"另存为 PDF"。
核心思路¶
- 收集样式 — 遍历页面中所有
<link>和<style>标签,构建完整 CSS 文本 - 克隆 DOM — 将目标区域
cloneNode(true)完整复制,保留原始样式 - 注入样式 — 将收集的 CSS 注入到打印窗口的
<head> - 触发打印 — 通过
window.print()唤起浏览器打印对话框,用户选择"另存为 PDF"
适用于任何 HTML / JS 环境:Vue、React、原生页面均可。
核心函数¶
收集所有样式表¶
function getAllStyleSheets() {
const doc = window.top.document
const sheets = []
for (const link of doc.querySelectorAll('link[rel="stylesheet"]')) {
sheets.push(link.sheet)
}
for (const style of doc.querySelectorAll('style')) {
if (style.sheet) sheets.push(style.sheet)
}
return sheets
}
合并为 CSS 文本¶
function buildStylesHtml() {
const styleSheets = getAllStyleSheets()
let cssText = ''
for (const sheet of styleSheets) {
try {
if (!sheet || !sheet.cssRules) continue
for (const rule of sheet.cssRules) {
cssText += rule.cssText + '\n'
}
} catch (e) {
// 跨域样式表,用 @import 兜底
try {
const href = sheet.href
if (href) cssText += `@import url("${href}");\n`
} catch (_) {}
}
}
return cssText
}
打印函数¶
/**
* 将指定 DOM 区域打印为 PDF
* @param {string} selector - CSS 选择器,不传则打印整个页面
*/
function printToPdf(selector) {
const dom = selector
? document.querySelector(selector)
: document.body
if (!dom) {
console.error('未找到目标 DOM:', selector)
return
}
const clone = dom.cloneNode(true)
const stylesHtml = buildStylesHtml()
const printContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>打印预览</title>
<style>
@page { margin: 10mm; }
* { box-sizing: border-box; }
body {
margin: 0;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
@media print {
body { padding: 0; }
}
</style>
<style id="__injected-styles__">
${stylesHtml}
</style>
</head>
<body>
<div id="print-target"></div>
<script>
document.getElementById('print-target').innerHTML = ${JSON.stringify(clone.innerHTML)};
window.addEventListener('load', function () {
window.print()
window.addEventListener('afterprint', function () { window.close() })
setTimeout(function () { window.close() }, 3000)
})
<\/script>
</body>
</html>
`
const printWindow = window.open('', '_blank', 'width=900,height=700,scrollbars=yes')
if (!printWindow) {
alert('弹窗被拦截,请允许本页面弹出窗口后重试')
return
}
printWindow.document.open()
printWindow.document.write(printContent)
printWindow.document.close()
}
使用方式¶
// 打印整个页面
printToPdf()
// 打印指定选择器区域
printToPdf('#my-report')
printToPdf('.invoice-container')
printToPdf('div.invoice-wrapper')
流程图¶
printToPdf(selector?)
│
▼
document.querySelector(selector) 查找目标 DOM
│
▼ (未找到 → console.error)
│
cloneNode(true) 克隆目标 DOM
│
收集所有 CSS 样式表
buildStylesHtml()
│
构造打印窗口 HTML
│
window.open 新窗口
│
写入 HTML → document.write()
│
触发 window.print()
│
用户在打印对话框选择"另存为 PDF"
│
afterprint 或 3s 兜底 → 窗口关闭
注意事项¶
- 弹窗拦截 —
window.open可能被浏览器拦截,需要用户允许弹出窗口 - 跨域样式 — CDN 等第三方样式表无法读取
cssRules,会使用@import url()兜底(依赖目标资源允许跨域) - 字体一致性 — 打印窗口字体依赖用户系统,建议使用 Web 安全字体或嵌入字体以确保一致性
- 打印边距 — 通过
@page { margin: 10mm }统一设置,按需调整 - afterprint 兼容性 — 部分浏览器不支持
afterprint,使用 3 秒定时器兜底关闭
适用场景¶
- 报表、数据看板导出 PDF
- 表单、合同、发票等打印需求
- 任意 Web 页面生成可存档的 PDF 文件
通用前端打印方案,Obsidian 笔记存档。