打印样式控制
特别注意事项
- 打印设置使用的是物理单位,所以尺寸最好不要用像素(
px),可以用pt也可以用cm - 隐藏不需要的或是次要的内容。
display:none; - 浏览器默认情况下并不能打印出 CSS 中的背景内容,只有当浏览器被设置为可以打印背景的情况下才能打印出背景
页面大小
size 的默认值为 auto
定义页面大小时,可通过 size 调节大小,定义大小同时会去除默认页脚页眉。默认的具有几个尺寸供选择。
- A5 :A5 的规定大小等同于 14.8cm x 21.0cm
- A4 :A4 的规定大小等同于 21.0cm x 29.7cm
- A3 :A3 的规定大小等同于 29.7cm x 42.0cm
- B5 :B5 的规定大小等同于 17.6cm x 25.0cm
- B4 :B4 的规定大小等同于 25.0cm x 35.3cm
- letter :letter 的规定大小等同于信纸大小,常用英尺单位 8.5in x 11in
- legal :legal 的规定大小等同于法律用纸大小,常用英尺单位 8.5in x 11in
- ledger :ledger 的规定大小等同于账簿大小,常用英尺单位 11in x 17in
自定义页面大小
css
@page {
size: A3;
}
css
@page {
size: 29.7cm 42cm;
}
对于默认的给定的尺寸,可以控制页面的横向「landscape」及纵向「portrait」,奇怪的是,对于自定义尺寸的,横纵向的控制是无效的,并且导致样式失效。
正确的写法
css
@page {
size: A3 landscape;
margin: 3.7cm 2.6cm 3.5cm; /* 国家标准公文页边距 GB/T 9704-2012 */
}
错误的写法
css
@page {
size: 29.7cm 42cm landscape;
}
注:定义页面大小后,无法通过设备再次调节(如谷歌浏览器的打印页面的设置大小将无效)。
单位
单位(cm,in 英寸)
1 inch = 2.54 cm
1cm = 96/2.54 ≈ 37.80 px
1px = 2.54/96 ≈ 0.0265 cm
100px = 2.65 cm
A4 纸的标准尺寸为:
21.0cm * 29.7 cm
在 96DPI 分辨率下,其对应的像素尺寸大约为:
794px * 1123px
px:pixel,像素,屏幕上显示的最小单位,用于网页设计,直观方便;
pt:point,是一个标准的长度单位,1pt = 1/72 英寸,用于印刷业,非常简单易用;
em:即%,在 CSS 中,1em = 100%,是一个比率,结合 CSS 继承关系使用,具有灵活性。
PPI(DPI):pixel(dot)per inch,每英寸的像素(点)数,是一个率,表示了“清晰度”,“精度” 。
px = pt * DPI / 72
毫米转 px
不同 dpi 显示器的毫米对应的 px 是不同的。但 css 支持设置毫米为单位,那么此时就可以取巧,创建一个 div 设置宽度为1毫米,再获取到这个 div 的宽度,那么这个值就是你显示器的1毫米对应的px值。pt 也是转 px 也是一样的道理
ts
/**
* 获取当前显示器1毫米对应的像素值
*/
function getOneMmsPx() {
// 创建一个1mm宽的元素插入到页面,然后坐等出结果
let id = `mm_${new Date().getTime().toString()}`
let div = document.createElement('div')
div.id = id
div.style.width = '1mm'
div.style.position = 'absolute'
document.querySelector('body')!.appendChild(div)
// 原生方法获取浏览器对元素的计算值
let mm1 = document.getElementById(id)!.getBoundingClientRect()
const mmPx = mm1.width
div.parentElement?.removeChild(div)
return mmPx
}
参考文章:使用 js 换算 mm,cm,px
调试打印样式


针对打印写特定的样式
- 通过指定 style 或 link 标签的 media 属性为 "print",即表示应用到打印样式
html
<link rel="stylesheet" src="print.css" type="text/css" media="print" />
- 指定 media="print" 的 css 外部文件(link 引入),会仅在打印时被浏览器下载并渲染。
html
<style type="text/css" media="print">
/* ... */
</style>
@media print媒体查询
在 CSS 内,使用媒体查询 @media print 将样式只应用到打印上。
css
@media print {
/* ... */
}
保持一致的最简单的方法是使用 media="all" 属性,即所有设备都使用当前样式
html
<link rel="stylesheet" src="print.css" type="text/css" media="all" />
html
<style type="text/css" media="all">
/* ... */
</style>
处理页眉/页脚
去除页眉页脚
css
/* 去除页眉 */
@page {
margin-top: 0;
}
/* 去除页脚 */
@page {
margin-bottom: 0;
}
/* 页眉和页脚全去掉 */
@page {
margin: 0;
}
自定义页眉页脚
通过 fixed 定位实现
p.s. 可能会造成自定义页眉和页脚覆盖正文内容的问题
position: fixed; top: 0; 的元素会在每页开头出现
position: fixed; bottom: 0; 的元素会在每页结尾出现
通过 table 实现
当表格完整充满至少 1 页时,thead 会循环出现在每页的页头,tfoot 会循环出现在每页的页尾
p.s.
- 最外层的布局需要改为使用 table 布局
- tfoot 会紧随内容,如果最后一页内容或者只有一页内容时,tfoot 不会是你想象中的页尾效果。不过这本来就是利用特性来卡 bug。算是非常规手段
html
<table>
<thead>
<th>
<div class="header">自定义页眉</div>
</th>
</thead>
<tbody>
<tr>
<td>
<div class="content">...</div>
</td>
</tr>
</tbody>
<tfoot>
<th>
<div class="footer">自定义页脚</div>
</th>
</tfoot>
</table>
将上下边距设置为 0,结合 table 布局和 fixed 元素解决最后一页页脚问题
- 将上下边距设置为 0
margin-top:0;margin-bottom:0; - 通过
table的thead和tfoot对页眉和页脚进行占位(占据高度和宽度,但不设置内容),下面的示例代码用的是空格进行占位,实际开发中应该通过 css 设置高度 - 使用 fixed 元素实现真正的页眉页脚,fixed 元素本来会导致覆盖页面的实际内容,但由于使用了 thead 和 tfoot 对每页纸的头部和尾部进行了高度占位,但实际又没内容,所以 fixed 只是覆盖了空白区域,而这个空白区域就是故意留给 fixed 元素的
html
<div class="positionFixedHeader">fixed定义方式实现的自定义页眉</div>
<div class="positionFixedFooter">fixed定义方式实现的自定义页脚</div>
<table>
<thead>
<th>
<div class="header"> </div>
</th>
</thead>
<tbody>
<tr>
<td>
<div class="content">...</div>
</td>
</tr>
</tbody>
<tfoot>
<th>
<div class="footer"> </div>
</th>
</tfoot>
</table>
自定义页码
首先可以肯定的回答普通情况下完全无法实现自定义页码。
首先可以肯定的回答普通情况下完全无法实现自定义页码。
首先可以肯定的回答普通情况下完全无法实现自定义页码。
可是看网上,好像也有实现的文章呀?
网上这类文章分两类:
第一类:css 伪类方式
各大浏览器根本没有实现这个标准,所以是无效的。如何判断有没有实现这个标准?使用@bottom-center到can i use网站上查,根本找不到任何内容。所以可以断定各大浏览器没有一个实现过这个方案
css
@bottom-center {
content: '第' counter(page) '页';
}
第二类: css 的 counter(page) 方式
和第一个有点类似但这种文章不再使用@bottom-center
而是使用::after伪类, 如下面代码的样子。首先counter在content属性是可用的。但是通常不会写page是怎么来的。counter用法详见:MDN: counter()。
css
.footer ::after {
content: '第' counter(page) '页';
}
通过 MDN 的文档可以理解到,上面代码的中的page应该类似一个变量的作用,会根据元素出现的次数,进行累加。乍一看,好像可以实现自定页码了,但其实不然。要通过这种方式实现自定义页码,那你必须确保有一个元素在每一页都会出现!再乍一想,fixed 元素的页眉页脚不是在每页都出现了嘛,这次稳了!这么想就大错特错了。fixed 元素只是刚好在打印预览界面体现出这种特性。但实际你的 html 元素还是只有两个。所以page这个变量只会自增一次。于是又回到如何确定哪个元素会在每页都出现的问题,如果需要自适应内容的情况下那是肯定无法实现的(所谓自适应内容,就是每页内容长度不固定),只有能严格控制每页的内容情况下。如:一个section标签,就表示一页,且单个section标签内的内容绝对不会大于1页,那此时可以控制从第二个section标签开始就强制打印预览换页,然后通过css的counter记录section的数量,从而达到间接获取到页码的效果。可既然都可以通过统计section标签数量的方式,计算总页数了,那还用使用 css 的counter? js 计算不是更方便。剩下的问题,就是如何将页码设置到页脚位置,理论上页脚位置可以通过:纸张高度对应的px值 - 字体大小px 公式得到,但这里纸张高度对应px值,不同 dpi 的显示器是不同的。没有一个精确的换算方式。即使采用本文上面章节中介绍的毫米转 px方式,得到的也是一个近似值,只适用于特殊场景,并无法通用
第三类:完全 js 计算方式
这种方式依然有问题。
- 首先 mm 转 px,不知道转换公式。另外限定为在
96dpi的设备var totalPages = Math.ceil(document.body.scrollHeight / 1123); //842px A4 pageheight for 72dpi, 1123px A4 pageheight for 96dpi,这段代码也是有问题的。document.body.scrollHeight和打印预览的总高度不一定一致,因为在打印预览中可能部分元素被强制换页了。所以document.body.scrollHeight不一定等于所有打印纸连接起来的高度,因此计算出来的总页数,和打印预览中看到的总页数,可能不一致pageNumberDiv.style.top ="calc((" + i +" * (297mm - 0.5px)) - 40px)"; //297mm A4 pageheight; 0,5px unknown needed necessary correction value; additional wanted 40px margin from bottom(own element height included)这段代码存在一个魔术值0.5px,这个魔术值在他的代码中可能运行正常,但在你的代码中,可能就不正常。即使你通过调试的方式也试出了一个魔术值,但这个魔术值可能只适合某类数据,在另一类数据上就无效了。
此 javascript 将在页面的右下角添加绝对定位的 div 页面编号。
A4高度= 297mm = 1123px(96dpi)
html
<html>
<head>
<style type="text/css">
@page {
size: A4;
margin: 0;
}
body {
margin: 0;
}
</style>
</head>
<body>
<script type="text/javascript">
window.onload = addPageNumbers;
function addPageNumbers() {
var totalPages = Math.ceil(document.body.scrollHeight / 1123); //842px A4 pageheight for 72dpi, 1123px A4 pageheight for 96dpi,
for (var i = 1; i <= totalPages; i++) {
var pageNumberDiv = document.createElement("div");
var pageNumber = document.createTextNode("Page" + i +" of" + totalPages);
pageNumberDiv.style.position ="absolute";
pageNumberDiv.style.top ="calc((" + i +" * (297mm - 0.5px)) - 40px)"; //297mm A4 pageheight; 0,5px unknown needed necessary correction value; additional wanted 40px margin from bottom(own element height included)
pageNumberDiv.style.height ="16px";
pageNumberDiv.appendChild(pageNumber);
document.body.insertBefore(pageNumberDiv, document.getElementById("content"));
pageNumberDiv.style.left ="calc(100% - (" + pageNumberDiv.offsetWidth +"px + 20px))";
}
}
Lorem ipsum....
</body>
</html>
代码片段来自 关于 CSS:打印 html 时在页面上打印页码
强制打印背景
-webkit-print-color-adjust是一个在浏览器中强制打印背景颜色和字体颜色的 css 属性,当打印出来的某些元素的背景颜色没有被显示时,可以使用-webkit-print-color-adjust:exact
css
body {
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
超链接处理
打印默认只会显示出链接的文字,而丢失真实的链接内容 —— url 路径。
要在打印时保留下链接的 url,可以借助 :after 伪类,并添加 content,将 url 显示在链接后面。
如:
css
@media print {
a[href*='//']:after
{
content: ' (' attr(href) ') ';
color: red;
}
}
a[href*='//'] 仅将外部链接(或url标准链接)在打印时,显示在链接文字的后面
内部链接通常用于导航或内部索引,一般不需要打印。如果想将所有链接的 url 在打印时显示出来,可以改为如下样式:
css
@media print {
a:after {
content: ' (' attr(href) ') ';
}
}
page-break-inside 或 page-break-before 或 page-break-after 不生效问题
如上这些属性只对 块级元素且定位方式为:relative 或 static的元素生效!
上面这句话什么意思?
即如果元素默认不是块级元素(如:div默认是块级元素,span默认是行内元素),也没有手动将display设置为block,则如上 css 属性不会生效。常见的问题是对display:flex的元素设置page-break-inside, page-break-before, page-break-after, 但没有生效,因为这些 css 属性要生效,display必须为block, 设置成了flex自然就不起作用了
另外position必须为relative或static, relative 我们经常使用,这没什么,主要是static, static是 HTML 元素的默认值,即没有定位,遵循正常的文档流对象。即当不对 html 元素设置position时,默认的positoin值就是static
html th width 无效 解决方法
html
<th style="width:100%">无效</th>
<th style="width:200px !important">无效</th>
<th style="min-width:200px !important">有效</th>
手动控制页面中断/分页
如果想要在某个元素后,或元素前分页中断,可以使用 page-break-after 和 page-break-before 属性。
如:实现每三个 div 元素则分页打印
css
div:nth-child(3n) {
page-break-after: always;
}
page-break-before,page-break-after,page-break-inside (CSS 2.1):用于控制元素之前、之后或之中是否分页,没有生成盒子的块元素不会生效。
page-break-before、page-break-after 属性支持 auto、always、avoid、left、right、recto 和 verso
auto默认值,表示既不强制分页也不禁止分页always、avoid表示在该元素之前(或之后)强制或禁止分页left、right表示在该元素之前(或之后)强制分页,使得下一页出现在左页或右页recto、verso页面进度从左至右时,分别与right和left一致;反之与left和right一致
css
thead,
tfoot {
display: table-row-group;
}
image,
thead,
tfoot,
tr,
th,
td {
page-break-inside: avoid;
}
orphans,windows
orphans 和 windows 用于指定在页面的底部或顶部,元素中允许剩余的最少行数,默认为 2 行。
css
.orphans {
windows: 2;
orphans: 2;
}
实践
- “白纸黑字”--避免不必要的背景颜色、加深文字颜色等
- 避免打印次要的内容,比如导航栏、侧边栏等
- 链接后显示链接地址
- 做好分页,避免标题、表格单元格等换行
css
@media print {
@page {
size: A4 portrait;
margin: 3.7cm 2.6cm 3.5cm;
}
h1 {
page-break-before: always;
}
h1,
h2,
h3,
h4,
h5,
h6,
thead,
tfoot,
tr,
th,
td,
li {
page-break-inside: avoid;
}
body {
background-color: white;
color: black;
}
nav,
aside {
display: none;
}
a::after {
content: '(' attr(href) ')';
}
thead,
tfoot {
display: table-row-group;
}
}
page-break-inside 属性仅支持 auto 和 avoid,表示在元素内允许或禁止分页。
避免元素在内部中断/分页
有一个最重要的问题,就是元素中断,由于元素高度跨度的问题,被中断为在两页显示,尤其是图片,一部分在上一页显示,另一部分在下一页显示。显然是不行的(除非你想这样)。
page-break-inside: avoid; 可以避免元素或图片在内部中断,显示在两页上
css
img {
page-break-inside: avoid;
}
div {
page-break-inside: avoid;
}
应用文档打印的@page规则
@page 规则用于打印文档,但只能修改 margin,orphans,widow 和 页面中断等属性。而且需要考虑所用浏览器是否支持。
打印的页面模型



页面外边距盒子需要在 @page 下使用,使用起来和伪类类似,也包含 content 属性。
css
@page {
/* 页面内容区域底部添加一条 1px 的灰线 */
@bottom-left, @bottom-center, @bottom-right {
border-top: 1px solid gray;
}
/* 页脚中间显示格式如 "第 3 页" 的页码 */
@bottom-center {
content: '第' counter(page) '页';
}
}
注:常见浏览器都不支持该属性
@page 边距
用于纸张打印的 page margin 单位,推荐使用 cm 或 in
css
@page {
margin-top: 2cm;
margin-bottom: 2cm;
margin-left: 2cm;
margin-right: 2cm;
}
@page 的伪类
:first应用于打印时的首页样式
css
@page :first {
margin-left: 5cm;
margin-top: 8cm;
}
@page :left和@page: right双面打印中的样式
@page :left 双面打印中所有的左侧页;@page :right 双面打印中所有的右侧页。
css
@page :left {
margin-left: 3cm;
margin-right: 4cm;
}
@page :right {
margin-left: 4cm;
margin-right: 3cm;
}
:blank打印文档中的空白页
css
@page :blank {
@top-center {
content: 'This page is intentionally left blank';
}
}
如何阻止页面打印
纯 css 阻止页面打印
css
@media print {
body {
display: none;
}
}
css
@media print {
#print {
display: block;
}
#noprint {
display: none;
}
}
通过 js 阻止打印
js
window.addEventListener('beforeprint', function (e) {
confirm('当前页面不允许打印!')
window.location.reload()
})
window.matchMedia("print") 事件方法中巧妙实现阻止打印
使用 window.matchMedia("print") 添加事件方法,通过在事件方法中添加一个阻塞方法,阻止打印对话框(或 print screen)对当前页面内容的加载,由于无法加载页面也就无法进行打印。 阻止打印的 matchMedia("print") 事件方法:
js
window.matchMedia('print').addListener(function () {
alert('当前页面不允许打印')
})
print-js
安装与使用
pnpm add print-js
vue
<template>
<div id="printTable"></div>
<div @click="bindPrint">打印</div>
</template>
<script>
import print from "print-js"
export default {
data () {
return {
}
},
methods:{
bindPrint( () => {
printJS({
printable: "printTable",
type:'html',
header:null,
targetStyles:['*'],
style:"@page {margin:0 10mm}"
})
})
}
}
</script>
参数配置
| 参数 | 类型 | 说明 | 可选值 | 默认值 |
|---|---|---|---|---|
| printable | string/object | pdf 或图像 url、html 元素 id 或 json 数据对象 | null | null |
| type | string | 打印的类型 | pdf, html, image, json and raw-html | |
| header | string/boolean | 用于 HTML、图像或 JSON 打印的标题。会被放置在页面顶部。接受文本或原始 HTML。 | null | true |
| headerStyle | string | 要应用于标题文本的可选标题样式 | null | 'font-weight:300;' |
| maxWidth | number | 最大文档宽度(以像素为单位)。根据需要更改此项。打印 HTML、图像或 JSON 时使用 | null | 800 |
| css | string | 传递一个或多个 css 文件 URL,这些 URL 应应用于正在打印的 html。值可以是具有单个 URL 的字符串,也可以是具有多个 URL 的数组。 | null | null |
| style | string | 允许我们传递一个自定义样式的字符串,该字符串应用于正在打印的 html。 | null | null |
| scanStyles | boolean | 设置为 false 时,库不会处理应用于正在打印的 html 的样式。使用 css 参数时非常有用。 | true, false | true |
| targetStyle | string | 默认情况下,打印 HTML 元素时,库仅处理某些样式。此选项允许您传递要处理的样式数组。示例: ['padding-top', 'border-bottom'] | null | null |
| targetStyles | string | 与“targetStyle”相同,这将处理一系列样式中的任何样式。例如:['border', 'padding'],将包括'border-bottom', 'border-top', 'border-left', 'border-right', 'padding-top'等。也可以传递['*']来处理所有样式。 | null | null |
| ignoreElements | string[] | 接受打印父 html 元素时应忽略的 html ID 数组。 | null | [] |
| properties | string | 打印 JSON 时使用。这些是对象属性名称。 | null | null |
| gridHeaderStyle | string | 打印 JSON 数据时网格标题的可选样式 | null | 'font-weight: bold;' |
| gridStyle | string | 打印 JSON 数据时网格行的可选样式 | null | 'border: 1px solid lightgray; margin-bottom: -1px;' |
| repeatTableHeader | boolean | 打印 JSON 数据时使用。设置为 false 时,数据表标题将仅显示在第一页中。 | true | |
| showModal | 启用此选项可在检索或处理大型 PDF 文件时显示用户反馈。 | null | null | |
| modalMessage | string | showModal 设置为 true 时向用户显示的消息。 | null | 'Retrieving Document..' |
| onLoadingStart | ()=>void | 加载 PDF 时要执行的函数 | null | null |
| onLoadingEnd | ()=>void | 加载 PDF 后要执行的函数 | null | null |
| documentTitle | string | 打印 html、图像或 json 时,这将显示为文档标题。 | null | null |
| fallbackPrintable | 打印 pdf 时,如果浏览器不兼容(请检查浏览器兼容性表),库将在新选项卡中打开 pdf。这允许您传递要打开的不同 pdf 文档,而不是传递到“可打印”中的原始 pdf 文档。如果在备用 pdf 文件中插入 javascript,这可能很有用。 | null | null | |
| onPdfOpen | 打印 pdf 时,如果浏览器不兼容(请检查浏览器兼容性表),库将在新选项卡中打开 pdf。可以在此处传递回调函数,发生这种情况时将执行回调函数。在某些情况下,如果您希望处理打印流、更新用户界面等,它可能会很有用 | null | null | |
| onPrintDialogClose | 浏览器打印对话框关闭后执行回调函数。 | null | null | |
| base64 | boolean | 打印作为 base64 数据传递的 PDF 文档时使用 | false |
添加水印
print-js打印指定dom的原理是将指定dom的outterHTML内容,整个复制到了ifarme中, 让后调用 iframe 的 print 方法实现打印,所以,无论你外面是否有加水印,到了 iframe 中都有可能没有水印,因为 iframe 中的内容并不包含水印内容,那如果要加水印,那就需要在print-js复制指定 dom 的outterHTML内容之前,先使用canvas生成图片,然后将图片转为 base64 编码,并将其设置为指定 dom 的背景图片,这样 print-js 再复制指定 dom 的outterHTML就已经包含水印的背景图片了
ts
function onPrint() {
addWatermark('你好张三', document.getElementById('printImageContent') as HTMLElement)
has = true
printJS({
printable: 'printImageContent',
type: 'html',
header: null,
scanStyles: false,
css: [`${baseUrl}print-css/dist/complex-print.css`],
})
}
ts
/**
* @params str 水印文字
* @param dom 待添加水印的dom节点
*/
export function addWatermark(str: string, dom: HTMLElement) {
const canvas = document.createElement('canvas')
canvas.width = dom.offsetWidth
canvas.height = dom.offsetHeight
canvas.style.display = 'none'
const cans = canvas.getContext('2d')
if (!cans) return
cans.rotate((-20 * Math.PI) / 180)
cans.font = '16px 宋体'
cans.fillStyle = 'red'
cans.textAlign = 'left'
cans.textBaseline = 'middle'
cans.fillText(str, 0, 100)
// 将canvas内容转为base64编码
const picBase64 = canvas.toDataURL()
// 将canvas的base64编码图像,作为dom的背景图片
dom.style.backgroundImage = `url(${picBase64})`
// 避免水印背景图在打印预览的时候被人为去掉
Object.assign(dom.style, {
'color-adjust': 'exact',
'print-color-adjust': 'exact',
'-webkit-print-color-adjust': 'exact',
})
}
参考资料:
vue-print-nb 的使用,没打印时没水印,打印预览页面设置水印
VUE 项目实践——浏览器打印时 canves 打印预览空白问题
关于前端使用 JavaScript 无法实现 canvas 打印问题的解决
jquery.watermark.js 在网页中添加水印,打印时水印背景不见了,办法来了
el-table 打印问题收集
解决 vue-print-nb 打印 el-table,不同分辨率下,打印显示不全的问题
echarts 打印问题收集
vue 项目中遇到的打印,以及处理重新排版后不显示 echarts 图片问题
打印插件问题收集
解决 vue-print-nb 打印 el-table,不同分辨率下,打印显示不全的问题
参考资料
CSS @meida print @page … 打印的另类样式写法
如何设置页面打印的 CSS 样式【浏览器打印】?以及禁止用户打印的两三种方法
解决 vue-print-nb 打印 el-table,不同分辨率下,打印显示不全的问题