打印为什么多一张空白页:从屏幕布局到纸张布局
后台系统的打印功能,最常见的投诉是:页面预览只有一张凭证,打印出来却多了一张空白页。
这时候不要急着缩小字号或者随意调整缩放比例,而应该先检查:实际的打印文档里,有哪些东西占用了空间?
本文基于一个 PC 后台近期两组打印修复的静态分析,核对日期为 2026-10-23。原项目使用 Print.js,包含单条打印和批量打印。本文没有执行真实的打印机验收。
读完这篇你会
- 理解打印库为什么会让“屏幕上正确”的样式在打印时失效
- 找出固定高度、留白、边距这些“看不见”的占位
- 写出只在两份内容之间分页的打印样式
- 设计一套覆盖单条、批量、不同内容长度的验收矩阵
1. 原理:往固定大小的行李箱里装东西
屏幕上的页面像一个可以无限延伸的房间:内容多了,往下滚就行。纸张则像一个固定大小的行李箱:每张纸能装多少东西是固定的,装不下的部分就要放进第二个箱子(第二页)。
问题在于,很多“看不见”的东西也占空间:固定的容器高度、底部留白、内外边距、浏览器默认的页边距、打印时的页眉页脚。它们单独看都很小,加在一起刚好超过一页,就多出了一张几乎空白的纸。
2. 坑一:屏幕上的样式,没有进入打印文档
现象:页面上明明清除了边距、设置好了样式,打印出来却完全不一样。
原因:打印库可能会在一个 iframe 里创建新的文档,把页面节点和计算后的样式复制进去。外层页面的样式、默认的边距和组件的 scoped 样式,不一定会按预期出现在打印文档里。
修复:Print.js 提供了 style、css、scanStyles 和 targetStyles 等参数。排查时,要看实际传给打印文档的规则,而不是只看原页面的 CSS。Print.js 文档
这次修复主要做了三件事:
- 清理打印文档中
html、body的默认边距; - 去掉为屏幕预览设置的固定高度和底部留白;
- 只在相邻的两份凭证之间强制分页。
3. 坑二:一张空白页,可能是很多个小尺寸加起来的
| 占用空间的来源 | 常见的误判 |
|---|---|
| 容器的固定高度 | “正文很短,所以一定不会溢出” |
| 页面底部的留白 | “留白里没有内容,所以不占打印空间” |
| 内外边距和边框 | “只要测量表格正文的高度就行” |
| 浏览器的页边距 | “A4 的尺寸就是内容能用的尺寸” |
| 打印的页眉页脚 | “预览里没有,所以不影响打印” |
近期的凭证修复移除了内容区的固定高度;明细修复则清理了打印容器和表格的底部空间。它们处理的是不同来源的占位,不能简单地归纳为“把 height 改成 auto”。
建议:把可打印的高度当成一份预算:纸张高度,减去实际的页边距和其他占用,再核对正文、标题、表格和签名区的总高度。具体的数值由目标浏览器和打印设置决定。
4. 坑三:每一份都强制分页,最后一份后面多了一页
现象:批量打印三张凭证,打印出来是四页,最后一页是空白的。
原因:给每一份凭证都无条件设置了“打印完后分页”,最后一份后面也会留下一个多余的分页要求。会不会真的出现空白页,还受浏览器和页面结构的影响,不能指望浏览器自动忽略它。
修复:只在两份内容之间分页。以下是通用的教学规则,假设打印根节点里只包含若干个 .sheet,没有其他尾部的兄弟节点:
css
@media print {
html, body {
margin: 0;
padding: 0;
}
.sheet {
height: auto;
margin-bottom: 0;
}
.sheet:not(:last-child) {
break-after: page;
}
}break-after: page 明确要求在当前元素之后分页。如果打印容器里还混有按钮、状态提示或其他兄弟元素,:last-child 指向的就不是最后一份凭证了,应该先整理结构,或者给最后一份内容显式地加上标记。MDN break-after
项目里通过打印专用的样式覆盖被复制过来的计算样式,部分规则使用了 !important。这是针对这条“样式复制链路”的局部处理,不应该把 !important 当成所有打印问题的通用解法。
5. 坑四:缩放比例被当成了万能常量
现象:一张报表调整缩放到 0.8 后正好一页,于是所有报表都用了 0.8;结果另一张报表的文字小到看不清,二维码也扫不出来。
原因:其中一类明细有固定的行数和较宽的表格,当前实现增加了缩放来适配目标纸张。这个数值和行高、字体、列宽、打印设置都有关,不能直接推广到另一张报表。
还要注意缩放可能发生在多个层次:maxWidth 参数用像素表达,纸张布局常用毫米,浏览器的打印对话框还可能再缩放一次。要明确是哪一层在缩放,避免多层叠加后文字过小、二维码无法识别。
内容明显超过一页时,应该设计表头重复、行的拆分和页码,而不是不断把整页压小。是否允许某一行跨页,也要按内容决定。
6. 坑五:单条修好了,批量打印还是有空白页
现象:修复后测试单条打印,一切正常;用户从列表批量打印时,空白页依然存在。
原因:单条打印和批量打印是两个入口,各自配置了打印参数。只改一个入口,另一个的问题依然存在。这次的代码同时修复了单条和批量两个版本。
建议:下一步把公共的打印规则集中起来,只把模板的差异作为参数传入。不过,近期的修复仍然是同步调整多个组件,不能说已经完成了统一的打印模块。
排查思路
- 先确认最后一页是不是纯空白:如果上面有一点点内容(比如一条边框、一个空的签名区),说明是真实的溢出,而不是多余的分页。
- 打印成 PDF 查看:用浏览器“另存为 PDF”快速检查页数和每页的内容,比真打印快得多。
- 在 DevTools 里模拟打印媒体:Rendering 面板的 Emulate CSS media type 选择
print,可以直接看打印样式下的布局(注意它看到的是原页面,不是打印库生成的 iframe 文档)。 - 检查打印文档:如果打印库使用 iframe,在打印前暂停或保留这个 iframe,检查其中实际的样式和结构。
建议验证矩阵
| 维度 | 至少检查 |
|---|---|
| 数量 | 单份、两份、多份,以及空数据 |
| 内容 | 最短的、刚好一页的、名称很长需要换行的、超过一页的 |
| 入口 | 单条打印和批量打印 |
| 设置 | 目标纸型、页边距、缩放、页眉页脚 |
| 输出 | PDF 预览的页数和实际打印的结果 |
验收时同时检查页数、有没有被裁切、文字大小和内容是否完整,不能只以“少了一页”作为成功的标准。
检查清单
- [ ] 打印样式确认进入了实际的打印文档
- [ ] 去掉了为屏幕预览设置的固定高度和底部留白
- [ ] 只在相邻的两份内容之间分页
- [ ] 缩放比例针对具体模板验收,没有多层缩放叠加
- [ ] 单条和批量入口都已修复并验证
- [ ] 超过一页的内容有表头重复和页码设计