用CSS打印样式把网页导出成整洁PDF的踩坑记录

阿乐
阿乐 管理员
发布于 2026-09-13 10:14 ·4 浏览 ·0 回复

前阵子接到一个需求:把系统里的报表页面直接导出成 PDF,交给客户存档。第一反应是用服务端无头浏览器渲染,但项目环境不允许装 Chromium,最后选了最朴素的路子——`window.print()` 配合 CSS 打印样式,让用户自己「另存为 PDF」。本以为半小时能收工,结果断断续续调了两天。这篇记录一下踩过的坑,希望能帮后来人少走点弯路。

屏幕样式和打印样式是两套世界观

最容易犯的错,是把打印当成「屏幕样式的截图」。实际上浏览器的打印渲染会重新计算一遍布局:视口宽度变成了纸张宽度,`vh` 变成了纸张高度,`position: fixed` 元素会在每一页重复出现,背景色和背景图默认不打印。所以第一件事不是写样式,而是在脑子里切换成「我在排一份 A4 文档」,而不是「我在调一个网页」。

调试方面强烈建议用 Chrome DevTools 的 Rendering 面板,打开「Emulate CSS media type: print」,比真的按 Ctrl+P 看预览反复切换快得多。

@page 才是真正的起点

@page {
  size: A4;
  margin: 16mm 14mm;
}

不写 `@page`,浏览器会用自己的默认边距,还会自动在页眉页脚塞上标题、URL、页码。用户如果没手动取消勾选,导出的 PDF 顶部就会多一行 `file:///...` 或者页面标题,非常难看。`@page` 的 margin 能挤掉这部分空间,但更稳妥的做法是在页面上提示用户关闭「页眉和页脚」,因为这条 CSS 并不能在 100% 的场景下生效。

另外 `@page` 只支持 `size` 和 `margin` 这类有限属性,别指望在里面写 `background`。想给首页加不同样式可以用 `@page :first`,但也仅此而已。

分页:break-inside 是玄学也是刚需

内容被从中间劈开是最影响观感的问题。常规解法:

.card, tr, .item, figure {
  break-inside: avoid;
  page-break-inside: avoid; /* 老引擎兜底 */
}
h2, h3 {
  break-after: avoid;
}

但这里有两个坑。第一,`break-inside: avoid` 对「自身高度超过一页」的元素无效,浏览器只能硬切,所以要给卡片设 `max-height` 或者干脆在大表格前手动插分页符(`.page-break { break-before: page; }`)。第二,父级如果有 `overflow: hidden` 或者被 flex/grid 布局包裹,`break-inside` 经常直接失效。我的经验是:打印时把外层容器切成 `display: block`,别用 flex 排布需要分页的区域。

背景色、图片和链接

* {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

不加这行,表头的浅灰底、状态标签的彩色背景全部消失,导出来一片惨白。加上之后颜色会保留,但也要注意别把深色主题直接打出来——喷墨打印机真的会把你那 200 毫升的黑色墨盒一次干掉。

链接的话,如果文档需要保留可点击性,可以这样展开 URL:

@media print {
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: #666;
  }
}

不过内部锚点链接和按钮类「伪链接」要排除掉,否则满屏都是丑丑的地址。

那些不该出现在纸上的东西

导航栏、侧边菜单、悬浮按钮、Toast 容器、`<video>`、iframe 广告位,统统要隐藏。我的习惯是在项目里约定一个 `no-print` 类,打印时统一清场:

@media print {
  .no-print, nav, aside, .toolbar, .toast-container {
    display: none !important;
  }
  body {
    background: #fff;
    font-size: 12pt;
    color: #000;
  }
}

注意用 `display: none` 而不是 `visibility: hidden`,后者仍然占位,会凭空多出半页空白,这个坑我踩过。

单位和小字号的忠告

打印样式里尽量用 `pt`、`mm`、`cm`,`px` 也能用但换算关系容易让人困惑。字号别低于 9pt,屏幕上看挺清楚的 12px,打到纸上就是蚂蚁打架。宽度尽量用百分比或者 `auto`,写死 `width: 1200px` 在 A4 上必然横向溢出,浏览器会把右边裁掉。

最后给一个最小骨架

@media print {
  @page { size: A4; margin: 16mm 14mm; }
  html, body { background: #fff; }
  .no-print { display: none !important; }
  .card, tr { break-inside: avoid; }
  h2, h3 { break-after: avoid; }
  a[href^="http"]::after { content: " (" attr(href) ")"; }
}

打印样式本质上是一门「被遗忘的 CSS 分支」,平时没人写,临时调起来又全是隐式规则。把上面这些点提前铺进项目里,导出 PDF 这件事基本就能从「两天」压缩到「两小时」。剩下的那点不完美,比如不同浏览器对 `break-*` 的支持差异、页脚页码得靠 JS 手动注入,就当作是这条路上必须留着的路标吧。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-289.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~