如何用css创建适合打印的页面

这是一篇翻译文章,原文请看How to Create Printer-friendly Pages with CSS

这篇文章更新于2020年,用来展示css样式打印时最近的最佳实践。

本文中我们将一起学习用css创建适合打印的web页面。

“谁会打印网页呢?”我听到了你的叫嚷声。相对来说很少有网页会展示在纸上,但是请想一下下面的这些场景:

  • 打印旅行的车票或者音乐会门票
  • 公示路标或者日程表
  • 保存在线阅读的内容
  • 在比较闭塞的地区获取信息
  • 在比较脏乱危险的地方使用数据,比如厨房或者工厂
  • 书写注释草稿
  • 为了记账打印网页收据
  • 为使用屏幕有困难的人群提供文档
  • 为拒绝使用网络模式提交作业的学校打印文档

不幸的是,打印页面可能有一些比较痛苦的回忆,比如:

  • 文字太小、太大、太虚
  • 行间距太小、太宽、甚至溢出页面
  • 有些内容被裁减甚至消失不见
  • 墨水被浪费在不需要的背景颜色和图片上去了
  • icon,菜单,广告等从未点击的内容被打印了出来

很多开发者都积极维护web的易用性,然而很少有人想到打印页面的易用性。

转换响应式页面到各种大小和方向的纸上展示出来是一个挑战。其实css几年前就可以控制打印样式了,实现基本的打印样式甚至不会超过1小时。接下来的内容描述了创建打印页面友好的支持和实用的选项。

添加打印样式

任选下面两种方式中的一种添加样式就可以:

  1. 附加在屏幕样式后面。以屏幕样式为基础,用打印样式来覆盖普通样式。
  2. 作为独立样式放置。屏幕样式和打印样式分离,在浏览器中单独加载。

具体两种样式怎么选择,要根据网站或者app的情况来定。就我个人而言,我通常选择方式1,以屏幕样式作为打印样式的基础。但有时候我也会分离应用的样式来彻底隔离输出的内容,比如一个会议预定系统,在屏幕上用时间来展现表格,但是在纸上是按照时间顺序打印出来。

打印样式可以在html的head标签中添加在标准样式后面,如下:

1
2
<link rel="stylesheet" href="main.css" />
<link rel="stylesheet" media="print" href="print.css" />

除了屏幕样式,在打印时print.css也将生效,会覆盖部分屏幕样式。

区分屏幕和打印样式,main.css将只在屏幕上生效,如下:

1
2
3
<link rel="stylesheet" media="screen" href="main.css" />
<link rel="stylesheet" media="print" href="print.css" />

除了上面两种方式外,打印样式可以通过媒体查询放在已有的css文件中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* main.css */
body {
margin: 2em;
color: #fff;
background-color: #000;
}

/* 在打印时覆盖样式 */
@media print {

body {
margin: 0;
color: #000;
background-color: #fff;
}

}

可以在任意一行插入@media print规则,这对于将相关联的代码组织在一起是非常有用的。如果有需要,在这种情况下照样可以将屏幕样式和打印样式完全分开,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*main.css*/

/*屏幕样式*/

@media screen {

body {
margin: 2em;
color: #fff;
background-color: #000;
}

}
/*打印样式*/

@media print {

body {
margin: 0;
color: #000;
background-color: #fff;
}

}

测试打印样式

在测试时每次都将打印内容打印在纸上既浪费又不环保,下面这些方法可以将打印内容复现在屏幕上。

预览打印

在浏览器上预览打印是一种非常可靠的方式,除了可以显示打印的样式,它还会将默认纸张上的分页情况显示出来。具体操作方法就是在浏览器右键,选择打印就可以看到。

在打印预览界面,你还可以将要打印的页面导出为pdf来保存或者预览。

Developer Tools

通过打开开发者工具,也可以预览打印样式,只是这种方式方式只能查看打印样式,无法看到具体纸张上的分页情况。

在chrome浏览器上,首先打开检查,然后选择更多工具,然后选择渲染,然后在模拟css媒体类型处选择打印,这时页面显示的就是打印样式。

在Firefox上,打开检查工具,然后点击切换媒体类型按钮即可。

改变媒体属性

<link>加载打印样式时,可以暂时将标签上的媒体属性media修改为打印screen

同样的,这种方法也不会显示换页,测试完后记得将媒体类型修改为print

移除打印时不需要的内容

通过diaplay:none属性将不需要打印的内容提前移除出去。通常菜单,网页头部和底部都是不需要打印的。因此可以像下面一样移除掉

1
2
3
4
/*print.css*/
header, footer, aside, nav, form, iframe, .menu, .hero, .adslot {
display: none;
}

有时候,遇到行内样式,使用display: none !important;是需要的。不推荐使用!important,但是在打印时为了覆盖基础样式,用一下也可以。

使用传统的线性布局,而不是flex或者grid

这么说让我有带你难过,但是flex和grid在一些浏览器上的支持确实不太好,如果遇到问题,请尝试使用display:block;或者盒子布局来调整,比如将宽度设置为100%来实现内容充满纸张。

打印实战

现在可以开始实践打印友好的样式来,下面是一些最佳实践推荐:

  • 确保是在白纸上打印黑色字体
  • 确保使用比较容易阅读的字体
  • 将字体调整到12pt或者更大
  • 在需要的地方配置padding和margin,标注单位cm和mm在配置时可能更加实用。

使用CSS Columns

标准的A4纸可能会导致文本行长过长且难以阅读,可以使用css Columns布局,比如:

1
2
3
4
5
/* print.css */
article {
column-width: 37em;
column-gap: 3em;
}

在上面的例子中,在水平空间中每一列将会是37em,如果纸张更宽,不许要增加媒体查询,新的列将会出来。

使用border而不是background

在打印时,可能有些部分有比较重的背景色,这个时候将背景色打印出来效果不好且浪费墨水,可以考虑用border来代替。

移除或者转换图片

用户很少愿意打印没有用的装饰和背景图片,可以考虑将没有的装饰和背景颜色隐藏掉,除非有print的class属性。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* print.css */
* {
background-image: none !important;
}

img, svg {
display: none !important;
}

img.print, svg.print {
display: block;
max-width: 100%;
}

理想情况下,打印的图片应该在浅色背景上使用深色,可以在 CSS 中更改 HTML 嵌入的 SVG 颜色,但在某些情况下打印出来的图片会变暗。

可以用CSS filter 来解决这个问题,比如:

1
2
3
4
/* print.css */
img.dark {
filter: invert(100%) hue-rotate(180deg) brightness(120%) contrast(150%);
}

处理后的结果如下:

添加附加内容

打印时经常会需要一些在屏幕上不需要的东西,这些额外东西可以利用伪类afterbeforecontent属性来实现,比如给一段文字后面添加一个链接:

1
2
3
4
/* print.css */
a::after {
content: " (" attr(href) ")";
}

对于更多的内容,可以考虑在html标签内的class属性上增加print值,print在屏幕上隐藏,在打印时显示。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 在屏幕上隐藏 */
.print {
display: none;
}

@media print {

/* 打印时显示 */
.print {
display: block;
}

}

分页

可以使用css的break-before,break-after属性在一些标签前后控制分页。这些属性当前浏览器支持的非常好,但是在一些老旧浏览器中,还得使用page-break-before,page-break-after属性。

break-before,break-after有以下一些值:

  • auto:默认值,页面按照出现顺序自动分页
  • avoid: 避免在页面上的某个区域,某个列内分页
  • avoid-page: 避免分页
  • page: 强制分页
  • always:同page,强制分页
  • left:强制分页,下一个页面时左页(双面打印时正面,单页打印时单数页面)
  • right: 强制分页,下一个页面时右页(双面打印时反面,单页打印时双页面)

比如,强制页面在h1标签前分页

1
2
3
4
/* print.css */
h1 {
break-before: always;
}

break-inside(老式浏览器中的page-break-inside)用来决定十分在某个内容区域内分页,属性值有:

  • auto:默认值,页面按照出现顺序自动分页
  • avoid: 避免内部分页,页面满了新起一页打印
  • avoid-page:避免内部分页,页面满了新起一页打印

break-inside可能比break-before,break-after更有用一些,它可以保证尽可能少的使用纸张,又不至于让图标等因为分页断开,比如不允许图片,图表分页

1
2
3
4
/* print.css */
table, img, svg {
break-inside: avoid;
}

windows属性用来配置页面顶部出现的最少行数,想象一个有五行的文本,想要在第四行分页,但是设置了widows: 3;,这时候就会在第二行处分页,将三行换到下一页。

orphans属性用来配置页面底部出现的最少行数。

box-decoration-break属性用来控制夸页面时页面border的显示方式,有以下属性值:

  • slice:默认值,顶部边框显示在第一页,底部边框显示在第二页
  • clone:复制边框,分页后边框在两个页面都显示

这个属性我测试时没有显示,应该是废弃了。

最后,@page属性虽然在一些浏览器中有限制,但是提供了一个配置页面的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* print.css */

/* 给整个页面增加margin */
@page {
margin: 2cm;
}

/* 只给第一个页面增加 */
@page :first {
margin-top: 6cm;
}

/* 单页配置 */
@page :left {
margin-right: 4cm;
}

/* 双页配置 */
@page :right {
margin-left: 4cm;
}

本篇结束