在纯前端开发中,把网页上的表格、单据或报告保存成PDF是一个常见需求。很多人第一时间会去找html2canvas加jsPDF这类组合库,但其实浏览器自带的window.print()方法就能完成这件事,而且不依赖任何外部脚本。只要当前页面能在打印预览里正确排版,用户选择“另存为PDF”就能得到文件。

一、window.print()的基本机制
window.print()是Web API里非常古老且稳定的方法,调用后会触发浏览器自身的打印流程。在桌面端Chrome、Edge、Firefox以及Safari中,打印对话框都提供“目标打印机”选项,其中就有“另存为PDF”或“导出为PDF”。也就是说,PDF的生成完全由浏览器内核完成,前端只负责让打印出来的内容符合预期。
这个方法没有返回值,也不会等待用户真正保存才往下走。为了知道用户什么时候关掉了打印框,可以用beforeprint和afterprint两个事件。beforeprint在打印前触发,适合先切换界面状态;afterprint在打印对话框关闭后触发,可以做恢复操作。下面是一个最简单的调用示例:
// 直接调起打印
function exportPdf() {
window.print();
}
// 监听打印结束,恢复页面
window.addEventListener('afterprint', function () {
console.log('用户已关闭打印对话框');
});
这种原生方式的优点是零体积、零维护成本,缺点是所有样式都必须通过打印媒体查询来控制,不能像库那样用Canvas先截图再拼装。但对于普通文档类页面来说,原生打印的清晰度反而更高,因为它是矢量文本而非位图。
二、用打印样式隔离无关内容
实际页面通常有侧边栏、顶部导航、操作按钮,这些在PDF里多半是多余的。CSS的@media print就是专门处理打印样式的规则。在打印环境下,我们可以把不需要的元素直接隐藏,同时调整纸张边距和字体大小。
下面是一段典型的打印样式代码,放在普通样式表后面即可。注意打印样式里要用pt或mm这类物理单位,比px更符合纸张习惯。通过给body设定特定类,还能在打印时切换整体布局:
/* 平时隐藏,打印也不需要 */
@media print {
.sidebar,
.top-nav,
.action-bar {
display: none !important;
}
/* 让内容区占满纸张 */
.main-content {
width: 100%;
margin: 0;
padding: 10mm;
}
/* 避免表格被截断 */
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
}
如果只想打印页面中某个容器而不是整个body,可以结合beforeprint事件,在打印前把其他兄弟节点临时隐藏,打印后再恢复。相比永久隐藏,这种临时方案更灵活,不影响屏幕上的正常浏览。
还有一种做法是克隆目标节点到新窗口再打印,但这会脱离当前页面的CSS作用域,需要把样式一并复制过去,反而麻烦。对于单页应用,直接用媒体查询隐藏非目标区域是最省事的。
三、局部打印与分页控制
当网页内容很长,比如一个包含几百条记录的清单,直接打印可能会在某行中间断页,导致表头丢失。CSS提供了page-break-before、page-break-after和page-break-inside来帮助控制。给每个小节标题加上page-break-before: always,就能强制从新页开始。
如果页面用了框架或路由,URL可能是类似http://127.0.0.1:8080/report这样的地址,打印时浏览器页眉默认会带上网址。可以在打印样式里用@page规则调整边距,但页眉文字通常由用户在打印设置里手动关闭,前端无法完全屏蔽。下面演示如何用@page统一纸张:
@page {
size: A4;
margin: 15mm;
}
@media print {
.section-title {
page-break-before: always;
}
.no-break {
page-break-inside: avoid;
}
}
对于需要重复出现的表头,可以在每个分页的表格前都放一份相同thead,或者利用display: table-header-group让浏览器自动在每页顶部重复表头。后者在Chrome中支持良好,能明显减少人工处理。
四、完整业务代码示例
假设我们有一个订单详情页,包含订单信息和商品列表,希望用户点按钮后只导出订单区块为PDF。下面用原生JS加打印样式完成。首先HTML结构里给操作栏和订单容器分好类:
<div class="action-bar">
<button onclick="printOrder()">导出PDF</button>
</div>
<div id="orderArea" class="order-area">
<h3>订单号:NO202405001</h3>
<table>
<thead>
<tr><th>商品</th><th>数量</th><th>单价</th></tr>
</thead>
<tbody>
<tr><td>鼠标</td><td>2</td><td>99</td></tr>
<tr><td>键盘</td><td>1</td><td>199</td></tr>
</tbody>
</table>
</div>
对应的脚本先定义打印前隐藏操作栏,打印后恢复。这里用class切换而非display,方便复用已有样式:
function printOrder() {
// 打印前隐藏按钮区
document.querySelector('.action-bar').style.display = 'none';
window.print();
}
window.addEventListener('afterprint', function () {
// 打印后恢复按钮区
document.querySelector('.action-bar').style.display = '';
});
再补一段CSS确保打印时只留订单区。这样用户点完按钮,在打印对话框选PDF,得到的文件就只有干净的订单内容。整个过程没有加载任何JS库,网络请求为零,在弱网环境也能稳定使用。
五、方案的局限与应对
原生打印方案并非万能。它不能精确控制PDF文件名,文件名由用户在保存框输入;也不能把多个独立页面自动合并成一个PDF,必须用户自己选纸张连续打印。另外,像echarts这类Canvas图表,在打印时若容器尺寸变化可能模糊,需要提前用chart.resize()固定尺寸。
如果业务要求后台批量生成PDF或自动邮件发送,那仍需要服务端用无头浏览器或PDF库处理。但针对纯前端“用户自己存一份”的场景,window.print()无库方案在开发效率、包体积和清晰度上都有明显优势。理解打印媒体查询和分页规则,就能用很少代码覆盖大多数导出需求。
window.printPDF导出无库方案修改时间:2026-08-06 13:57:38