作为一只深度“网瘾少年”,偶尔看到喜欢的网页内容,除了会选择使用笔记工具收藏、浏览器收藏夹库加star外,偶尔还会使用纸质打印保存,或者使用浏览器提供的网页转PDF功能留个备份。

然而提到打印,多数网页并不是都适合打印的。

打印网页结果和传统书籍的异同

两者相同点挺多,但简而言之:

基本都是图文混排的平面“图像”。

最关键的差异点:

网页中呈现的内容和最后想打印的结果可能差异很大,屏幕尺寸和要打印的纸之间的比例不同,网页中有大量的不需要打印的元素,甚至如果直接在打印机上预览页面,页面脚本可能无法正确的被执行(前端模版方案包含的页面内容可能被忽略掉)。

解决方案

  • 优选使用CSS提供的@media规则来针对特定设备来展示内容。
  • 次选使用JS来针对设备、屏幕宽度、UA来进行内容渲染和展示。
  • 在实现页面内容呈现的过程中,HTML结构中最大程度只保留页面必要内容。

CSS Media Query

这个方案实际上是相对比较稳健的,IE从6.0支持all, print, screen这三种基本的屏幕类型的样式的加载(link tag),其他浏览器基本天然支持。

MDN文档中对于Media Query的语法和特性描述已经很多,这种基础的事情就不赘述了,下面聊一下怎么去更好的定义页面内的样式。

以Bootstrap为例,在v3主流版本中,它的打印机样式,主要是依赖html5-boilerplate和自己的Print utilities中的声明来实现的。

/* ==========================================================================
   Print styles.
   Inlined to avoid the additional HTTP request:
   http://www.phpied.com/delay-loading-your-print-css/
   ========================================================================== */

@media print {
    *,
    *:before,
    *:after,
    p:first-letter,
    div:first-letter,
    blockquote:first-letter,
    li:first-letter,
    p:first-line,
    div:first-line,
    blockquote:first-line,
    li:first-line {
        background: transparent !important;
        color: #000 !important; /* Black prints faster:
                                   http://www.sanbeiji.com/archives/953 */
        box-shadow: none !important;
        text-shadow: none !important;
    }

    a,
    a:visited {
        text-decoration: underline;
    }

    a[href]:after {
        content: " (" attr(href) ")";
    }

    abbr[title]:after {
        content: " (" attr(title) ")";
    }

    /*
     * Don't show links that are fragment identifiers,
     * or use the `javascript:` pseudo protocol
     */

    a[href^="#"]:after,
    a[href^="javascript:"]:after {
        content: "";
    }

    pre {
        white-space: pre-wrap !important;
    }
    pre,
    blockquote {
        border: 1px solid #999;
        page-break-inside: avoid;
    }

    /*
     * Printing Tables:
     * http://css-discuss.incutio.com/wiki/Printing_Tables
     */

    thead {
        display: table-header-group;
    }

    tr,
    img {
        page-break-inside: avoid;
    }

    p,
    h2,
    h3 {
        orphans: 3;
        widows: 3;
    }

    h2,
    h3 {
        page-break-after: avoid;
    }
}

这里基本上只是简单的定义了一下规则来:去掉页面多余的颜色内容、避免自动分页、增强展示包含链接的内容。

Print utilities内容:

// Print utilities
//
// Media queries are placed on the inside to be mixin-friendly.

// Note: Deprecated .visible-print as of v3.2.0
.visible-print {
  .responsive-invisibility();

  @media print {
    .responsive-visibility();
  }
}
.visible-print-block {
  display: none !important;

  @media print {
    display: block !important;
  }
}
.visible-print-inline {
  display: none !important;

  @media print {
    display: inline !important;
  }
}
.visible-print-inline-block {
  display: none !important;

  @media print {
    display: inline-block !important;
  }
}

.hidden-print {
  @media print {
    .responsive-invisibility();
  }
}

如果某些内容不需要展示给打印机,那么只需要添加上述类名到HTML元素上就可以了,如果页面内容是由HTML模版+JS模版实现,那么可维护性其实有一些挑战。

进阶优化

先聊针对html5-boilerplate的优化:

 body {
    width: 100% !important;
    margin: 0 !important;
    padding: 0 !important;
    line-height: 1.45;
    font-family: PingFang SC, Lantinghei SC, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;
    color: #000;
    background: 0 0;
    font-size: 14pt
  }

  h1, h2, h3, h4, h5, h6 {
    page-break-after: avoid
  }

  h1 {
    font-size: 19pt
  }

  h2 {
    font-size: 17pt
  }

  h3 {
    font-size: 15pt
  }

  h4, h5, h6 {
    font-size: 14pt
  }

  h2, h3, p {
    orphans: 3;
    widows: 3
  }

  code {
    font: 12pt Courier, monospace
  }

  blockquote {
    margin: 1.2em;
    padding: 1em;
    font-size: 12pt
  }

  hr {
    background-color: #ccc
  }

  img {
    float: left;
    margin: 1em 1.5em 1.5em 0;
    max-width: 100% !important
  }

  a img {
    border: none
  }

  a:link, a:visited {
    background: 0 0;
    font-weight: 700;
    text-decoration: underline;
    color: #333
  }

  a:link[href^="http://"]:after, a[href^="http://"]:visited:after,
  a:link[href^="https://"]:after, a[href^="https://"]:visited:after {
    content: " (" attr(href) ") ";
    font-size: 90%
  }

  abbr[title]:after {
    content: " (" attr(title) ")"
  }

  a[href^="http://"] {
    color: #000
  }

  a[href$=".jpg"]:after, a[href$=".jpeg"]:after, a[href$=".gif"]:after, a[href$=".png"]:after {
    content: " (" attr(href) ") ";
    display: none
  }

  a[href^="#"]:after, a[href^="javascript:"]:after {
    content: ""
  }

  table {
    margin: 1px;
    text-align: left
  }

  th {
    border-bottom: 1px solid #333;
    font-weight: 700
  }

  td {
    border-bottom: 1px solid #333
  }

  td, th {
    padding: 4px 10px 4px 0
  }

  tfoot {
    font-style: italic
  }

  caption {
    background: #fff;
    margin-bottom: 2em;
    text-align: left
  }

  thead {
    display: table-header-group
  }

  img, tr {
    page-break-inside: avoid
  }

  code {
    font-size: 12pt !important;
    border: 1px solid #333;
  }

对于要打印的内容:

  • 进行尺寸调整,避免浪费纸张、打印结果大量留白。
  • 定义各平台默认字体,避免web-font加载失败,或者无法覆盖所有字符,打印结果混杂多种字体。
  • 针对代码标签配置等宽字体,特殊展示。
  • 差异化展示各级别标题。
  • 超链接仅展示HTTP(s)协议内容,以及图片内容,其他内容不进行展示。

针对bootstrap的优化:

@media print {

  // 列表元素页面
  body.page-type-home *,
  body.page-type-tag *,
  body.page-type-topic *,
  ... {
    display: none !important;
  }

  body.page-type-home:after,
  body.page-type-tag:after,
  body.page-type-topic:after,
  ... {
    content: "请勿浪费纸张打印列表页面 / Don't waste paper!";
  }

  // 全局不展示内容
  #J_footer-container,
  .comments-link,
  .page-comments-container,
  ... {
    display: none !important;
  }

  // 文章页面内容
  .page-type-post {
    // 阅读器元素隐藏
    .sr-only {
      display: none !important;
    }

    // 侧边栏隐藏
    .g-navbar-box {
      display: none !important;
    }
    // 页面主要内容
    #J_main-container {
      margin: 0 30px;
      .m-comments-link,
      .m-post-markdown,
      .post-bottom-meta-box,
      .page-navigation-container,
      .page-comments-container {
        display: none !important;
      }
    }

    // 页脚列表
    #J_footer-container {
      .nav.nav-pills {
        display: none !important;
      }
    }
  }
}
  • 对于随时会更新的列表内容,默认展示一句请勿浪费纸张打印列表页面 / Don't waste paper!,避免无意义打印。
  • 对于JS模版全局生成的增强性质的节点,进行隐藏。
  • 对于sr-*阅读器相关的内容,完全不进行展示。
  • 对于页面中无法进行交互的META元素进行隐藏。

划个重点细节:

<link rel="stylesheet" href="/common.css?v=DawnEve&t=20141208-3r" media="screen"/>

如果你把打印机样式和其他的样式都打包成一个文件,请记得取消media的设置,或者设置为all,如果像上面一样设置为screen,那么之前的工作就白费了。

或者你也可以根据设备进行样式的拆分,将打印机样式单独提取到一个地方,比如print.css,在保持之前的样式不变的情况下,使用下面的结构。

<link rel="stylesheet" href="/print.css?v=DawnEve&t=20141208-3r" media="print"/>

依赖JS的实现

参考detecting-print-requests-with-javascript的特性检测的方式,很容易包装一个你自己小模块。

不过这里除了可以用JS追踪用户是否进行了打印操作外,还可以稳定的添加当前设备模式的类名来给CSS进行操作举个简陋的例子:

var instance = null;
var $ = require('core');

function init() {
    if (instance) {
        return true;
    } else {
        instance = true;
    }

    var beforePrint = function () {
        $('html').addClass('print-mode');
    };
    var afterPrint = function () {
        $('html').removeClass('print-mode');
    };

    if (window.matchMedia) {
        var mediaQueryList = window.matchMedia('print');
        mediaQueryList.addListener(function (mql) {
            if (mql.matches) {
                beforePrint();
            } else {
                afterPrint();
            }
        });
    }
    window.onbeforeprint = beforePrint;
    window.onafterprint = afterPrint;
}

module.exports = {
    init: init
};

适用场景

  • 当你无法根据上一节对link标签的media进行特殊操作的时候。
  • 当你不想大量使用Media Query能力的时候。
  • 当你的用户主流设备恰好支持JS能力比例很高的时候。

使用上面的能力检测的方式,你也会很容易做到根据打印机设备来进行内容的差异化展示了。

不过既然使用JS了,偶尔可能会有点击按钮,调出系统对话框进行打印的需求。

常规做法是:

document.queryCommandSupported("print") &&
document.execCommand("print", false, null)

非常规做法是:

focus(), print()

如果在2017年你还要考虑IE678的话,那么可能你需要给页面添加一个iframe,再进行window.print()的调用了。

HTML结构优化

这个其实没有什么可说的了,记忆中有一篇老博客对这块有具体的描述,简单思路来说就是页面只展示基础的文章内容,任何修饰部分都交给JS来处理。

这样,当JS无法运行,或者运行会被阻塞的时候,页面所提供的内容依旧是完整的,并且可以大幅简化可运行JS设备的程序的复杂度,提升可维护性以及执行效率。

结束语

这套组合拳打完之后,基本你的网站就拥有了和Safari阅读模式一样的体验了,不论是直接阅读,还是打印阅读,相比较之前体验应该是有质的提升的。(可以用Safari体验一下本站 :D )

其他

  • 需要在修正网站后,补充图片对比。
  • 需要找回之前对于HTML页面结构优化的思考的文章链接。

–EOF