🔥如何设置页面打印的CSS样式【浏览器打印】?以及禁止用户打印的两三种方法

6,907 阅读8分钟

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

浏览器中打印的样式一直是一个比较难以处理的问题(尤其是IE时代),但是,随着现代web的发展,对于实现控制打印时的页面样式,已经提供了很多新特性。下面就介绍一下,如何控制浏览器中的打印样式。以及,在最后介绍几种禁止用户打印的可能方法!

实际浏览器打印,还是和直接通过本地应用调用打印机驱动打印,有着不小的差距,尤其是字体导致的打印清晰度问题。

浏览器打印还有一个很不错的功能点,就是打印时“另存为PDF”,尤其对于想要将网页内容保存为 PDF 格式使用,这是一个不可错过的功能(打印为PDF在样式上不会有太大的偏差,便于保存浏览)。

比如,另存网页为PDF,制作电子档文件(Ctrl + p 或 右键当前网页->菜单中选择“打印”,即可打开打印对话框):

用于打印的CSS

最常见的处理,打印时可能需要隐藏一些内容,比如 footer、header、sidebar 以及 广告推广内容 等。甚至需要为打印设置完全不同的样式和字体。

样式应用的 media 属性

用于答应的样式,可以通过指定 style 或 link 标签的 media 属性为 "print",即表示应用到打印样式

如下所示:

<link rel="stylesheet" src="print.css" type="text/css" media="print" />

指定 media="print" 的 css 外部文件(link引入),会仅在打印时被浏览器下载并渲染。

<style type="text/css" media="print">
    /* ... */
</style>

@media print 媒体查询

在 CSS 内,使用媒体查询 @media print 将样式只应用到打印上。

@media print {
  /* ... */
}

保持页面样式和打印样式一致的最简单方法

保持一致的最简单的方法是使用 media="all" 属性,即所有设备都使用当前样式

<link rel="stylesheet" src="print.css" type="text/css" media="all" />

<style type="text/css" media="all">
    /* ... */
</style>

打印时的链接处理

链接在 HTML 中几乎无处不在,通过 a 标签引入一个超链接。但是,

如果用于打印,默认只会显示出链接的文字,而丢失真实的链接内容 —— url路径。

要在打印时保留下链接的url,可以借助 :after 伪类,并添加 content,将 url 显示在链接后面。

如下,一个 a 标签应用打印的样式:

@media print {
    a[href*='//']:after {
        content:" (" attr(href) ") ";
        color: red;
    }
}

a[href*='//'] 仅将外部链接(或url标准链接)在打印时,显示在链接文字的后面(如上图所示)。

内部链接通常用于导航或内部索引,一般不需要打印。如果想将所有链接的url在打印时显示出来,可以改为如下样式:

@media print {
    a:after {
        content:" (" attr(href) ") ";
    }
}

页面中断(Page break)

如果想要在某个元素后,或元素前分页中断,可以使用 page-break-afterpage-break-before 属性。

比如,实现每三个 div 元素则分页打印:

div:nth-child(3n){
    page-break-after: always;
}

page-break-before: always; 也是一样,在元素的前面分页。

page-break-beforepage-break-after 的取值为: autoalwaysavoidleftright

避免在元素内部在内部中断(尤其是图片)

有一个最重要的问题,就是元素中断,由于元素高度跨度的问题,被中断为在两页显示,尤其是图片,一部分在上一页显示,另一部分在下一页显示。显然是不行的(除非你想这样)。

page-break-inside: avoid; 可以避免元素或图片在内部中断,显示在两页上。

img {
  page-break-inside: avoid;
}

div {
  page-break-inside: avoid;
}

应用于文档打印的 @page 规则

@page 规则用于打印文档,但只能修改margin,orphans,widow 和 页面中断等属性。而且需要考虑所用浏览器是否支持。

page模型

@page 对应一个页框模型,如下:

它是从 HTML 文档映射过去的。

结合 page 边距的模型:

page 边距

用于纸张打印的 page margin 单位,推荐使用 cmin

@page {
    margin-top: 2cm;
    margin-bottom: 2cm;
    margin-left: 2cm;
    margin-right: 2cm;
}

@page 的伪类

  • :first 应用于打印时的首页样式
@page :first{
    margin-left: 5cm;
    margin-top: 8cm;
}
  • @page :left@page: right 双面打印中的样式

@page :left 双面打印中所有的左侧页;@page :right 双面打印中所有的右侧页。

@page :left {
  margin-left: 3cm;
  margin-right: 4cm;
}

@page :right {
  margin-left: 4cm;
  margin-right: 3cm;
}
  • :blank 打印文档中的空白页
@page :blank {
  @top-center { 
        content: "This page is intentionally left blank";
    }
}

page盒子的大小size

size 指定纸张的大小,如:

@page {
  size: A4;
}
@page {
  size: A5;
}

Margin @ 规则

Margin at 规则只能用在 @page 媒体中,同时目前还处于CSS标准的定义中,将来也有可能被移除。了解即可。

比如,指定 Margin @ 显示 content(内容)【浏览器几乎都未支持】:

@page {
  size: A4;
  margin: 10%;

  @top-left {
    content: "我的故事";
  }
  @bottom-right {
    content: "页数:" counter(page);
  }
}

所有可能的参考规则如下:

@top-left-corner
@top-left
@top-center
@top-right
@top-right-corner
@right-top
@right-middle
@right-bottom
@bottom-right-corner
@bottom-right
@bottom-center
@bottom-left
@bottom-left-corner
@left-bottom
@left-center
@left-top

调试打印呈现

在开发者工具中,提供了模拟 打印布局 的方式。

F12 打开开发者工具,点击右侧小三点,“更多工具”(More tools)下,点击“绘制”(Rendering):

在打开的面板中,在模拟媒体类型下,选择“打印”(print),即可调试打印的样式。

【如何阻止页面打印?】

有一个比较极端的情况,有时候我们不想要用户打印当前页面(这个问题应该结合不允许用户复制页面内容,不允许打开开发者工具等一起解决,后续有机会再讨论)。

当然,这些都不能完全杜绝用户的打印,有很多办法可以跳过这些限制,获取页面内容,然后进行各种需要的操作。

纯CSS实现阻止页面打印

解决办法就是,在打印的 css 样式中,将 body 标签设置为 display: none;,打印文档不显示页面的任何内容。

@media print {
    body { display: none; }
}

这样打开打印页面,就会显示一片空白。从而无法打印。

不过这样显然不是很友好,用户看到一片空白,而不了解是不能打印导致的。因此,我们可以添加一个提示信息。

<body>
    <p id="print">当前页面不允许打印!</p>
    <div id="noprint">
        <!-- 真实的页面内容 -->
    </div>
</body>

在主样式中:

#print { display: none; }
#noprint { display: block; }

打印的样式中

@media print {
    #print { display: block; }
    #noprint { display: none; }
}

也可以放在 print.css 文件中:

<link rel="stylesheet" src="print.css" type="text/css" media="print" />
/* print.css 文件 */
#print { display: block; }
#noprint { display: none; }

显示效果如下:

使用js阻止页面打印

阻止 Ctrl + P 按键 和 浏览器右键菜单

由于在浏览器中打印的方式主要是通过 Ctrl + P,或者,右键菜单点击“打印”实现的。

Ctrl + P 的处理,是在 keypress 事件中,判断是否按下这两个键,如果是,调用 e.preventDefault() 阻止执行。

window.addEventListener("keydown", function (e) {
    if(e.ctrlKey && (e.key=="p" || e.key=="P")){
        e.preventDefault();
    }
});

取消打开右键菜单,同样的 e.preventDefault() 处理,放在 contextmenu 事件方法中。

还有一点,浏览器自身菜单栏中,也有一个“打印”菜单,我们根本无法控制

beforeprintafterprint 打印事件【阻止打印实现】

beforeprintafterprint 是与打印相关的两个事件,但是不支持取消

不过仍然可以巧妙的结合 document.write() 来实现。

window.addEventListener("beforeprint", function (e) {
    document.write('<div style="text-align:center;color:red;">当前页面不允许打印!</div>');
});

在打印预览窗口会显示“当前页面不允许打印!”的提示。

但是,由于是 document.write() 写入文档实现的,点击取消打印后,返回页面就丢失了原来的内容。

并且,点击“取消打印”,并不会触发 afterprint 事件。也就是,不能知道什么时候用户取消打印(来还原原来的页面内容)

还有一个解决办法,在 beforeprint 事件中,重新刷新当前页面,由于重新刷新,就不会打开打印的对话框了。

【都不是很优雅!】

下面通过 js刷新当前页面 实现:

window.addEventListener("beforeprint", function (e) {
    confirm("当前页面不允许打印!");      
   
    window.location.reload();
});

window.matchMedia("print") 事件方法中巧妙实现阻止打印【小hack,"arcane"方法】

后面在 How to trigger javascript on print event? 中的回答中,发现了一个阻止页面打印很"奥妙"的方法,即:

使用 window.matchMedia("print") 添加事件方法,通过在事件方法中添加一个阻塞方法,阻止打印对话框(或print screen)对当前页面内容的加载,由于无法加载页面也就无法进行打印。

阻止打印的 matchMedia("print") 事件方法:

window.matchMedia("print").addListener(function() {
            alert("当前页面不允许打印");
        })

同样,该阻塞方法也可以使用 location.reload() 等刷新当前页面的方式实现阻止打印。

通过此方式打开打印对话框时,会一直处于加载的状况,此时无论是点击"打印"还是"取消",都无法进行打印。

点击“打印”/“取消”后,会有弹窗提示 "当前页面不允许打印"。

注:暂时不知何种原因,会显示两次弹窗提示。原文以为是原文回答中提到的有多个 @media print {} CSS样式的原因,后面测试不是其导致。

beforeprint 中刷新,matchMedia("print") 中阻塞或刷新,可以实现阻止所有可能的浏览器打印方法进行打印。

参考