打印功能看起来是个小需求,但真正做起来坑不少。浏览器渲染屏幕和渲染打印用的是两套逻辑,屏幕上精心排版的页面,切到打印预览时经常面目全非。React项目由于组件化拆分,一个页面的DOM结构往往比传统页面更碎,打印问题也就更突出。本文从@media print媒体查询入手,一步步讲清打印样式的隔离方案和分页控制的细节。

一、@media print媒体查询的基本用法与React适配
打印样式的核心是@media print媒体查询,它里面的规则只在打印或打印预览时生效,屏幕显示完全不受影响。最基础的用法是隐藏打印时不需要的元素,比如导航栏、侧边栏、操作按钮:
@media print {
.no-print {
display: none !important;
}
body {
background: #fff;
}
}在React中,建议把no-print这类类名做成一个公共常量,比如导出一个PRINT_HIDDEN常量,所有需要隐藏的地方统一引用。这样后期如果想换成别的隐藏方式,或者改用CSS Modules作用域隔离,只需要改一处定义。
另一个React特有的问题是样式隔离。如果项目用了CSS Modules,打印样式也写在对应模块文件里即可,媒体查询同样生效。如果用的是styled-components或Emotion这类CSS-in-JS方案,写法稍有不同:
import styled from '@emotion/styled';
const Toolbar = styled.div`
@media print {
display: none;
}
`;有一点要注意:有些团队会把打印样式单独抽一个文件,在主入口全局引入。这样做的问题在于React组件按需加载时,打印样式可能先于组件加载,造成选择器对不上。更稳妥的做法是把打印样式跟着组件走,组件加载到哪里,打印规则就出现在哪里。
二、背景色丢失与彩色边框的打印问题
很多开发者第一次做打印时都会疑惑:明明设置了背景色,打印出来却是一片白。这其实是浏览器的默认行为——为了省墨,浏览器默认不打印背景色和背景图。解决办法有两个层面。
第一个层面是CSS层面,加上print-color-adjust属性强制保留颜色:
@media print {
.badge {
background: #1890ff;
color: #fff;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}第二个层面是用户设置,Chrome的打印面板里有一个背景图形勾选项,默认是关闭的。CSS的print-color-adjust: exact在Chrome和Firefox中能覆盖这个默认值,但部分旧版浏览器不一定支持,所以对于关键信息不要只靠颜色传达,比如状态标签除了颜色还要带文字,这样即使用黑白打印机打印也不会丢失信息。
此外要提醒一点,打印时的颜色尽量用纯色背景而不是图片背景。图片背景在打印时可能因为缩放出现锯齿,而且打印质量依赖图片分辨率,效果往往不如纯色填充稳定。
三、分页控制:让长表格和卡片不被硬切
分页是打印优化里最容易被忽视的部分。默认情况下浏览器会在页面装不下时直接截断,一个卡片的上半截在这页、下半截在下一页的情况非常难看。控制分页的核心属性有几个,先看最常用的:
page-break-inside: avoid:元素内部不允许分页,即尽量保证元素完整出现在一页内page-break-before: always:元素前强制分页,常用于每个章节另起一页page-break-after: always:元素后强制分页break-inside: avoid:新版标准属性,与page-break-inside作用相同,建议两个都写以兼容更多浏览器
在React列表渲染场景中,分页控制的典型应用是卡片列表和表格。比如打印订单列表时,每个订单卡片不允许被切断:
.order-card {
page-break-inside: avoid;
break-inside: avoid;
}
.section-title {
page-break-after: avoid; /* 标题后面不要直接分页,避免标题孤立在页尾 */
}page-break-after: avoid这个属性值得单独说一句。它解决的是标题孤儿问题:如果一个章节标题正好落在页面最底部,内容全在下一页,读起来会很奇怪。给标题加上这个属性,浏览器会尽量把标题和后面的内容排到同一页。
对于长表格,情况更复杂一些。表格行可以加page-break-inside: avoid防止一行被切断,但如果表格特别长,跨越多页是必然的。这时可以用thead的重复显示特性:打印时thead会在每一页自动重复,表头不用额外处理。要注意的是,如果给table整体设置了page-break-inside: avoid,浏览器发现整个表格放不进一页时反而会放弃该规则,直接硬切,所以长表格千万不要给整个table加avoid。
另外注意分页控制属性不是万能的。如果被设置了page-break-inside: avoid的元素本身高度超过一页纸,浏览器只能无视这个规则强行分页。所以对于超长内容块,合理的设计是允许分页,转而在内容结构上做切分,比如把大段内容拆成多个子块分别设置avoid。
四、React组件级的打印方案与页面边距设置
有时候需求是只打印页面中的某个区域,而不是整个页面。原生做法是开一个新窗口,把目标区域的HTML复制过去再调用打印,但这种方式会丢失React的事件和上下文,样式也要重新注入,维护成本高。更优雅的做法是利用@media print反向隐藏:打印时隐藏所有内容,只显示目标区域。
可以封装一个PrintArea组件,通过React的Portal和上下文来标记打印区域:
import { createContext, useContext } from 'react';
const PrintContext = createContext(false);
export function PrintArea({ children }) {
return (
<PrintContext.Provider value={true}>
<div className="print-area">{children}</PrintContext.Provider>
</PrintContext.Provider>
);
}上面代码里的JSX结构应该是外层div包裹Provider再包裹children,写组件时注意闭合正确。配合全局打印样式,打印时隐藏body下的直接内容,只保留print-area:
@media print {
body > div#root > *:not(.print-wrapper) {
display: none;
}
}页面边距通过@page规则控制,它可以设置纸张大小和页边距,甚至支持命名页面实现不同区域不同边距:
@page {
size: A4;
margin: 15mm;
}
@page landscape-page {
size: A4 landscape;
}调用打印时直接用window.print()即可,如果想在打印后做一些状态清理,可以监听afterprint事件。移动端WebView中window.print()可能不可用,需要走原生桥接方案,这是另一个话题了。
总结一下思路:先用媒体查询隔离屏幕与打印两套样式,再用print-color-adjust处理颜色保留,接着用分页控制属性保证内容块完整,最后用组件化方案实现按区域打印。把这几个层面组合起来,React项目里的打印体验基本就不会掉链子了。
React打印样式@media printCSS分页控制修改时间:2026-09-07 08:00:39