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

这是我参与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; }
    

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

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

    <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") 中阻塞或刷新,可以实现阻止所有可能的浏览器打印方法进行打印。

    The CSS @page at-rule can be used to apply certain properties and at-rules on paged media.

    How to print your HTML with style

    @page

    Window: beforeprint event

    分类:
    前端