我前阵子接了个内部管理系统的改造需求,有个弹窗里的结算单要支持打印,结果测试那边反馈了一个很经典的问题——调用window.print()后,预览只显示第一页,后面全是空白,而且弹窗的灰色半透明遮罩、圆角边框、右上角的关闭按钮全都出现在打印输出里了。这个问题在项目里反复出现,根本原因其实不复杂,但涉及浏览器打印机制的几个关键点,绕来绕去很容易踩坑。这篇文章就针对这两个现象,把原因拆透,再把实操方案完整贴出来,顺手整理一份排查清单。
1. 先搞清楚 window.print() 到底在打印什么
先说第一个问题,为什么打印只有第一页。很多人下意识以为是打印样式没写好,或者内容总高度不对,但实际真正的关键是——浏览器打印的默认行为,是渲染整个文档(document)在当前视图下的内容,而不是“从第一行到最后一个元素的完整高度”。
1.1 浏览器打印的真实渲染逻辑
window.print()这个 API 本质上没有任何参数,它不会接收一个“要打印的区域”或者“要打印的节点”。浏览器拿到这个调用后,会走一遍渲染流程:
- 先根据当前文档构建渲染树,包括所有
display不为none的元素。 - 如果存在
@media print样式,则按打印样式重新计算布局。 - 然后按分页规则把文档分割成若干“页”输出到打印机或预览窗口。
这里有个容易忽略的细节:在预览时,很多浏览器的打印视图会默认把内容缩放适应页面宽度。如果你的页面主体设置了固定宽度、固定高度,或者某个容器的高度是用100vh来定的,那打印引擎会按照分页高度截断内容。比如你最外层容器设置了height: 100vh; overflow: hidden;,打印时它就只能渲染这一屏的高度,后面的内容直接判定为不可见,自然只有第一页。
另一个高频场景是弹窗。弹窗在页面上是以position: fixed或者absolute的形式存在的,它的定位上下文是视口(viewport),而不是整个文档流。打印时如果没做特殊处理,打印引擎会把它当作一个“固定定位的气泡”渲染在当前页面上,而弹窗内部的滚动区域(比如overflow-y: auto的列表)同样只取当前可视高度。如果弹窗里要打印的数据特别长,超过一屏,那么滚动条以下的内容在打印 DOM 里是存在的,却因为容器高度限制被截断,这就是“只有第一页”最常出现的原因之一。
1.2 为什么“弹窗的样式边框和打印按钮”也会打印出来
这就更好解释了。弹窗本来就是页面 DOM 的一部分,它的边框、阴影、遮罩层、关闭按钮,都是渲染在文档里的真实元素。你用window.print()打印时,默认打印的是整个文档,弹窗只是文档里一个position: fixed的节点,它当然也会被画出来。
很多人一开始会有个错觉:弹窗是“浮”在页面上的,打印的时候应该只打印弹窗内部的内容。实际上浏览器没有一个“指定打印某个元素”的原始能力,打印的目标永远是整个文档。所以想要只打印弹窗里的内容,必须主动把“非目标内容”隐藏掉,再把弹窗内部的打印区域单独暴露出来。这个思路说起来简单,实际操作中有很多坑,后面会专门拆开讲。
2. 拨开现象看本质:分页、溢出与打印布局
要彻底解决“只有第一页”的问题,不能只靠调page-break,还要理解打印引擎是怎么决定哪些内容进入下一页的。
2.1 打印分页的核心规则
打印引擎分页的规则听起来很简单:内容的高度一旦超过当前页的可打印高度,就自动折行到下一页。但这个“高度”不是页面元素布局高度,而是打印页面物理高度减去页边距后得到的可打印区域高度。
举个例子,A4 纸纵向打印,物理高度约 297mm,如果你没有单独配置@page样式,浏览器默认会有页边距(不同浏览器默认值不一样,Chrome 约 12.7mm,Safari 约 18mm),也就是说可打印区域大约只剩 270mm 左右。你页面上如果有某个容器设置了height: 300mm,那这个容器在打印时是超过一页的,内容能不能正确分页,取决于容器内的元素布局方式。
- 如果容器内部是普通文本流,内容会自动断开换页。
- 如果容器内部是一个高度固定的表格,表格行高可能被截断,或者整个表格被挪到下一页。
- 如果容器内部是依赖滚动加载的内容,超出部分压根不会被打印。
我遇到过一次特别典型的案例:弹窗里放了一个div,设置了height: 600px; overflow-y: auto;,然后把表格数据放进去。用户滚动时能看到完整数据,但打印时这个div只会渲染可视区域那部分,后面的全部丢失。即使你把整个弹窗内容复制到别的地方,只要外层容器高度有上限、且开启了overflow: hidden或auto,打印引擎就不会把溢出的内容“流”到下一页。这就解释了为什么预览时第一页正常,后面却空空如也。
2.2 高度与表格跨页的特殊性
表格是打印分页的重灾区。因为表格本身是一个高度自适应的块级元素,但在打印时,浏览器对表格的分页处理相对保守:
- 表格行不能拆分到两个页面时,整行会被移动到下一页。
- 表格外层有固定高度容器时,超出的行可能直接丢失,而不是继续分页。
- 单元格内有
page-break-inside: avoid设置时,同样可能造成内容被整体挤到下一页。
很多开发者以为设置了table-layout: fixed、加了个page-break-after就万事大吉。实际上,关键点在于表格的直接父容器有没有正确的height: auto和overflow: visible。打印时如果外层容器高度没有解除限制,表格再长也打印不全。
这里给一个重要提示:在打印样式里,最好把所有中间容器的height重置为auto,把overflow重置为visible,然后让内容自然流动,浏览器才能按正常分页规则把内容拆到多页。这也是解决“只有第一页”最通用的釜底抽薪方案之一。
2.3@media print中经常被忽略的全局因素
@media print里不是只能写颜色和隐藏元素,还有很多全局影响因子。比如:
* { -webkit-print-color-adjust: exact; }可以保留背景色。html, body { height: auto; }解除视口高度限制。@page { size: A4; margin: 10mm; }控制纸张和页边距。#root / body * { visibility: hidden; }+ 目标区域visibility: visible的方案。
这些全局设置如果不统一处理,打印机引擎很容易按屏幕布局来分页,出现各种“第一页正常、后面空白”的怪现象。我处理这类问题时会先把所有可能限制高度的属性在@media print里全部重置一边,再根据目标区域做精确控制。
3. 解决“打印只有第一页”的实操方案
接下来是实战环节。我会从最简单的排查入手,再给出一套能直接上线的做法。在敲代码之前,建议先在浏览器里按 F12 打开开发者工具,切到打印预览(有些浏览器叫“打印模拟 / Print media”),逐层检查目标内容外层每个元素的尺寸和overflow表现。如果某个中间容器高度被卡死,预览里就能肉眼看到内容被拦腰截断。
3.1 最粗暴也最通用的一招:重置容器尺寸
这个方法适用于弹窗、抽屉、卡片等几乎所有复杂容器。在项目的全局 CSS 文件里加一段:
@media print { html, body { height: auto !important; overflow: visible !important; } /* 弹窗根节点 */ .print-modal { position: static !important; height: auto !important; max-height: none !important; overflow: visible !important; box-shadow: none !important; border: none !important; } /* 弹窗内部的可滚动容器 */ .print-modal-body { height: auto !important; max-height: none !important; overflow: visible !important; } }然后把你要打印的内容所在容器的类名替换成.print-modal。这两组样式干的事情非常直接:
- 把
html和body的高度重置为自动,保证整个文档可以自然撑开。 - 把弹窗从
fixed定位改成static,让它回归文档流,不飘在页面顶部。 - 去掉弹窗的滚动限制和边框阴影,保证内容全部展开。
这样做完之后,再看打印预览,绝大多数“只有第一页”的情况都会消失。因为内容已经开始按文档流自然分页了。如果你的场景比较特殊,弹窗内容仍然被截断,那就需要逐层排查:从打印预览里看到哪个元素的高度异常,就给它单独加height: auto !important。
提示:
!important在这种场景下不是滥用,而是必要手段。很多 UI 框架的组件库(比如各类弹窗组件)内部样式优先级很高,靠普通选择器覆盖不动,只能用!important强行重置。
3.2 处理表格跨页丢失的问题
如果内容里包含长表格,重置外层容器还不够,还得单独处理表格:
@media print { table { width: 100%; border-collapse: collapse; } tr { page-break-inside: avoid; } td, th { word-break: break-word; } }这里tr { page-break-inside: avoid; }的意思是一行不要被劈成两半。如果你不希望任何行被拆开,又想让表格完整多页显示,可以把这段从tr转移到td,或者直接不设avoid,让浏览器自由断行。一般而言,财务报表、结算单这类表格,行内容比较短,我建议每行保持完整,用户体验更好。
有朋友可能会遇到另一个奇葩现象:表格第一页正常,第二页以后每行都重叠、文字叠在一起。这通常是表格单元格内有固定高度的内部容器导致的。优先检查td内部有没有div被设置了height,打印时把它也改为auto。
3.3 分页控制的三个常用属性
控制打印分页的属性主要有三个:page-break-before(元素前分页)、page-break-after(元素后分页)、page-break-inside(元素内部是否允许分页)。实际使用中我一般这样组合:
@media print { .print-header { page-break-inside: avoid; } .page-new-start { page-break-before: always; } .avoid-break { page-break-inside: avoid; } }需要特别注意一个点:page-break-inside: avoid并不是所有浏览器都严格生效,Safari 对它的支持就相对弱一些。如果某个区块实在无法避免跨页截断,可以考虑调整 DOM 结构,把不能拆开的部分合并成一个独立容器。还有一个笨办法,就是把“第一页”单独拆成一个容器打印,后面内容再拆成另一个容器打印,用page-break-before: always强制分页,但这样会增加维护成本,不太建议一上来就用。
4. 让弹窗“干净地”打印:只输出想要的内容
现在解决第二个高频率痛点:打印输出里带着弹窗的边框、遮罩、按钮。先说清楚底层逻辑——只要你没有在打印前把弹窗之外的元素隐藏,浏览器就会把整个文档画出来。要干净地打印弹窗内容,核心思路只有一个:打印时让页面里只有目标内容,其他元素全部隐藏。
4.1 方案一:隐藏非打印区域(最推荐)
在@media print样式里,把除了目标区域以外的所有元素都隐藏。实现上有两个思路:
思路 A:给目标区域加类名,并隐藏它的兄弟节点:
@media print { body * { visibility: hidden; } .print-area, .print-area * { visibility: visible; } .print-area { position: absolute; left: 0; top: 0; width: 100%; } }这个方案很巧妙:所有内容visibility: hidden后,文档占位还在,但不可见。再把.print-area及它的子元素重新设为visible,只有这个区域会被打印出来。这里有一点要留意:隐藏元素依然占据布局空间,所以要把.print-area设置成绝对定位,从原来的位置提出来,放到页面左上角,避免它被挤到某个奇怪的位置。
思路 B:直接display: none隐藏不需要的内容:
@media print { .popup-overlay, .popup-header, .popup-close-btn, .popup-footer .btn-group, .sidebar, .navbar { display: none !important; } }这个方案比较直观,但需要把页面里所有不需要打印的模块都列出来。如果项目里有很多弹窗、很多样式类,维护起来会有点累。建议结合思路 A 一起用:先全局隐藏,再按需显示目标区域,最后单独补几个display: none清除漏网之鱼。
实际操作中,我推荐使用visibility方案来做“只打弹窗内容”的场景,因为不需要把所有无关节点都枚举一遍。只要把弹窗内部真正要打印的容器加上.print-area类即可,其他自然全部隐藏。
4.2 方案二:动态切换类名,在调用 print 前调整 DOM
有些老项目没有完善的打印样式表,或者目标区域样式跟正常展示样式强耦合,光靠 CSS 可能覆盖不干净。这时候可以在window.print()调用前,动态往body加一个类名,打印结束后再移除。示例:
function printElement(selector) { const content = document.querySelector(selector); if (!content) return; const cloneContent = content.cloneNode(true); // 用一个临时容器包裹目标内容 const printContainer = document.createElement('div'); printContainer.id = 'print-container'; printContainer.appendChild(cloneContent); document.body.innerHTML = ''; document.body.appendChild(printContainer); window.print(); // 打印结束后恢复原有内容需要额外备份 }但这种直接替换body.innerHTML的做法太暴力,会把原来页面的所有事件监听和 DOM 状态全部破坏,不推荐用于在线系统。更稳妥的方式是使用iframe方案,只把目标内容克隆到 iframe 里打印,不影响原页面。
4.3 方案三:使用 iframe 隔离打印(实测最稳)
我对这个方案比较熟悉,它在处理“弹窗内打印复杂表格”时非常稳。原理很简单:创建一个隐藏的iframe,把要打印的内容克隆进去,然后在 iframe 里调用打印。因为 iframe 是一个独立的文档,打印时只打印 iframe 内部内容,不会受原页面样式影响,也不会把弹窗边框打进去。
先看实现代码:
<button onclick="printContent('print-modal-body')">打印结算单</button>function printContent(selector) { const sourceContent = document.querySelector(selector); if (!sourceContent) return; const iframe = document.createElement('iframe'); iframe.style.position = 'fixed'; iframe.style.right = '0'; iframe.style.bottom = '0'; iframe.style.width = '0'; iframe.style.height = '0'; iframe.style.border = '0'; iframe.style.visibility = 'hidden'; document.body.appendChild(iframe); const iframeDoc = iframe.contentDocument; iframeDoc.open(); iframeDoc.write(` <!DOCTYPE html> <html> <head> <title>打印内容</title> <style> /* 打印样式直接内嵌,保证 iframe 内部引用 */ ${getPrintStyles()} </style> </head> <body> ${sourceContent.outerHTML} </body> </html> `); iframeDoc.close(); // 等待 iframe 内容渲染完成后再打印 setTimeout(() => { iframe.contentWindow.focus(); iframe.contentWindow.print(); // 打印后移除 iframe document.body.removeChild(iframe); }, 300); } function getPrintStyles() { return ` body { font-size: 14px; color: #333; } table { width: 100%; border-collapse: collapse; } .print-area { border: none; box-shadow: none; } @media print { ... } `; }这个方案的好处很明显:
- 原页面完全不动,弹窗、按钮、遮罩一样都在,只是打印目标变成 iframe。
- iframe 里的样式可以独立控制,不会受原页面全局 CSS 干扰。
- 如果目标内容里有图片,需要注意图片路径用的是相对路径的话,在 iframe 里可能无法正常加载,需要把相对路径转成绝对路径。
注意:setTimeout(300)不是拍脑袋写的,因为 iframe 的内容写入和渲染是异步的,如果立刻调用print(),某些浏览器的 iframe 还没完成样式布局,打印出来可能丢样式。比较稳的做法是监听 iframe 的onload事件,但前提是 iframe 里能触发 onload。由于我们用write方法写入完整 HTML,可以在iframeDoc里加一个<script>window.onload = function() { ... }</script>,但每个浏览器表现略有差异。实测下来,setTimeout300ms 在很多场景下都够用,如果是包含大量图片或复杂表格的内容,可以加长到 500ms。
这里有个经验:iframe 方案里,如果目标内容有图片,最好先把图片转成 base64,或者确保图片不会在打印瞬间被裁剪。因为打印时网络请求可能来不及完成,图片位置会留下空白。我经常用一个小技巧:打印前遍历目标区域里的图片,如果它已经
complete且naturalWidth > 0,就把它转成 canvas 再导出 dataURL 换进去。但要注意,跨域图片转 canvas 会被污染,这类场景要先让后端配好 CORS 头。
4.4 方案四:window.open 新窗口打印
iframe 方案虽然稳,但仍有部分旧内核浏览器对 iframe 内的打印兼容性不够。这时候可以用新窗口方案:打开一个空白窗口,写入目标内容,然后在新窗口里调用打印。逻辑跟 iframe 很像,但视觉上会弹出一个新的独立窗口。
function printInNewWindow(selector) { const content = document.querySelector(selector).outerHTML; const newWin = window.open('', '_blank', 'width=800,height=600'); newWin.document.write(` <html> <head> <title>打印</title> <style>${getPrintStyles()}</style> </head> <body>${content}</body> </html> `); newWin.document.close(); newWin.focus(); setTimeout(() => { newWin.print(); }, 300); }这个方案的缺点也很明显:新窗口会被浏览器弹窗拦截策略拦截(如果是用户手动点击触发的函数,一般没问题);如果用户浏览器禁用了弹窗,这个方案直接失效。另外,新窗口打印完成后,需要手动关闭窗口,否则会在桌面留一个多余的空白窗口。所以它更适合内部系统这种可控环境,不太适合对外暴露的页面。
单纯从开发效率和维护成本来看,我自己的选择顺序是:@media print隐藏非目标区域 > iframe > 新窗口。前两者能覆盖绝大部分业务场景。
5. 从“弹窗边框和按钮被打进去”到“干净打印”的完整实战
说了这么多,这里给一个完整的弹窗打印实现案例。场景是:页面上有个弹窗,里面展示一张结算单,用户点弹窗里的“打印”按钮,期望只打印结算单内容,不要遮罩、不要弹窗边框、不要按钮,而且内容超过一页时能正确分页。
先看 HTML 结构:
<div class="modal-overlay" id="modalOverlay"> <div class="modal-content" id="modalContent"> <div class="modal-header"> <span>结算单</span> <button class="close-btn" onclick="closeModal()">关闭</button> </div> <div class="modal-body" id="printArea"> <h2>供应商结算单</h2> <table> <thead> <tr><th>项目</th><th>金额</th><th>备注</th></tr> </thead> <tbody> <tr><td>商品A</td><td>1000</td><td>第一期</td></tr> <!-- 很多行数据 --> </tbody> </table> </div> <div class="modal-footer"> <button onclick="printModal()">打印</button> </div> </div> </div>接着是打印样式:
@media print { /* 隐藏全局非打印区域 */ body * { visibility: hidden; } /* 只显示打印区域 */ #printArea, #printArea * { visibility: visible; } #printArea { position: absolute; left: 0; top: 0; width: 100%; max-height: none; overflow: visible; } /* 弹窗边框和遮罩在打印时不需要显示 */ .modal-overlay { background: transparent !important; } .modal-content { border: none !important; box-shadow: none !important; } .modal-header, .modal-footer, .close-btn { display: none !important; } /* 解决高度截断问题 */ html, body { height: auto !important; overflow: visible !important; } }最后是打印逻辑:
function printModal() { window.print(); }就这么简单。核心逻辑全都在@media print里。
你可能注意到,打印按钮如果放在.modal-footer里,会被display: none隐藏,所以打印出来的内容里不会出现那个按钮。弹窗的圆角边框因为.modal-content被设置成border: none,也不会出现。遮罩层的背景改成透明,不会出现灰蒙蒙的一片。
如果还需要更精细的控制,比如“打印时隐藏页面上所有其他按钮,但保留品牌水印”,可以把隐藏范围改成body :not(.brand-watermark)之类的选择器。但尽量别搞太复杂,越简单越不容易出错。
6. 常见疑难杂症排查记录
问题并不总是“第一页 + 弹窗边框”这两个,实际项目中还会有各种衍生问题。这里列一下我常遇到的疑难杂症和处理思路,按优先级排序。
6.1 打印预览只有第一页,内容全部丢失
排查步骤:
- 打开打印预览,检查第二页是否存在。
- 若第二页存在但空白,看第一页最后一个元素下方有没有一个高度巨大的空白区域,这通常是某个容器设置了很大的
margin-bottom或padding-bottom。 - 若第二页完全不存在,直接在开发者工具 Elements 面板里看目标内容外层容器,确认是否被设置
height: 100vh或overflow: hidden。 - 优先在
@media print里重置html、body和所有中间容器的height: auto; overflow: visible;。
如果页面里用了position: fixed的弹窗,还要把弹窗根节点置为position: static,我前面提到过,这个非常关键。
6.2 打印只有表格第一行/第一块内容
这个诡异问题的常见元凶是:表格或内容外层有一个display: flex的容器,且flex-direction: column,里面子项设置了flex: 1。在打印布局里,flex 容器的高度计算可能会异常,导致子项溢出部分不显示。处理办法是在@media print里把目标容器改成display: block; height: auto;。
另外,还有一种常见场景是表格的thead被重复打印,而tbody只有第一页。通常是因为tbody里某个tr设置了break-inside: avoid,导致整行被推到下一页,视觉上像是“中间缺了一段”。可以尝试把page-break-inside从tr移到td,副作用是同一行可能被拆开,但至少内容不丢。
6.3 Chrome 打印正常,Safari 只有第一页
Safari 是打印问题的高发地。它对height、overflow的处理跟 Chrome 存在差异,对position: fixed的元素尤其敏感。一个通用策略是:在@media print里把要打印的内容从“固定定位容器”里移出来。我之前在做某跨平台系统时遇到过这种情况,最后就是通过 iframe 方案绕过去的,因为 iframe 内部是一个全新的文档,没有原页面的定位上下文。
6.4 打印出的内容出现巨大空白页
常见原因:
page-break-after: always加多了,每个区块后面强制加了一页。height: 100vh的元素后面紧跟着下一个元素,而打印时可打印区域高度略小于 100vh,导致元素溢出到第二页。- 隐藏元素使用了
visibility: hidden,但它仍然占位,导致页面高度被撑高。
排查方式:把打印样式里的page-break-*全部注释掉,看空白页是否消失。如果消失,就是分页控制的问题;如果没消失,就检查隐藏元素导致的占位。
6.5 打印时背景色/背景图片消失
浏览器默认不打印背景色和背景图。解决办法:
@media print { * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } }这个属性不仅能保留背景色,还能让一些基于背景色实现的斑马纹表格打印出来更好看。
6.6 打印预览里没有出现分页线,只有一整条超长内容
有些浏览器在用户没设置@page size时,会横向排布内容,导致预览看起来像一长条。建议在打印样式里显式声明:
@page { size: A4 portrait; margin: 10mm; }如果是 IE 内核浏览器,还需要写@page section { size: 8.5in 11in; }这种方式。不过现在 IE 基本不在支持范围了,主要针对 Chromium 内核写就行。
6.7 打印日期/页码
很多业务系统希望打印时自动带页码,可以借助 CSS 的@page和content实现,但兼容性一般。最稳的做法还是后端生成 PDF,或者前端在打印前用 canvas 把页码画到底部。如果只是内部系统用,直接在打印预览里让用户勾选“页眉页脚”也行,省时省力。
6.8 弹窗内容打印出来错位,原页面内容混入
这种情况通常是@media print里用了position: absolute定位目标区域,但目标区域不在 body 的直接子节点下,而是嵌在一个带transform的容器里。打印时transform会创建新的包含块,导致absolute定位错乱。解决办法很简单:把目标区域用position: fixed或干脆保持原位置,不要去动它的定位。
回顾我实际处理过的项目,最典型的一次是:目标区域在弹窗内层,弹窗本身有transform: translate(-50%, -50%)实现居中,打印时我给目标区域设了position: absolute; top: 0; left: 0,结果它跑到了整个页面的左上角,但尺寸和原点错位。去掉transform后,一切正常。所以结论是:打印样式的核心不是移动位置,而是隐藏非目标内容;能不位移元素就不要位移。
7. 我建议的方案优先级和后续扩展
最后聊聊我的个人倾向:在纯前端打印这个方向上,如果是简单弹窗内容,优先使用@media print隐藏非目标区域,这是最干净、最符合浏览器原生机制的做法。如果遇到复杂场景,比如大表格、图表、多页日历、动态生成内容,优先使用 iframe 隔离打印,因为它能把“原页面崩坏”的风险降到最低。
有时候需求不是“打印当前弹窗内容”,而是“把整个页面某个区域导出成 PDF”。这时候纯前端打印就不够用了,更合适的是引入生成 PDF 的类库,把 DOM 转成 PDF 文件,这样用户可以下载而不是调用系统打印对话框。但是纯前端生成 PDF 在复杂表格上又容易排版错乱,这时候更推荐让后端根据数据源直接生成 PDF,前端只负责调接口下载。说到底,打印不是一个“一劳永逸”的功能,越复杂的排版需求,越需要提前想清楚边界。
我在实际开发中的一个小习惯是:在项目里封装一个独立的打印工具模块,统一处理“打开打印预览、还原页面状态、销毁临时 iframe”这些逻辑。这样后续遇到新弹窗、新页面需要打印时,直接调用统一方法,不用每个地方都复制一份打印代码,也不容易在细节上踩坑。
如果后续还要继续扩展,可以考虑给打印内容增加分页页眉页脚、每页底部显示“第 x 页 / 共 y 页”、自定义纸张大小等功能。这些功能用纯 CSS 实现兼容性有限,比较靠谱的方式还是借助打印类库或后端渲染。不过对于大多数系统里的“结算单打印”“订单明细打印”来说,前面讲的方法已经够用了。
最后再分享一个我在处理打印问题时的心得:先在浏览器开发者工具的打印模拟模式下调通样式,再真机打印测试。很多前端细节在预览模式里看不到,比如实际打印机的页边距和纸张大小,预览模式无法完全模拟。开发完以后,一定要拿不同浏览器过一遍,尤其别漏了 Safari。打印这套东西,永远没有“这段代码全世界通用”的说法,多测试、多备方案才是正道。