本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2017年07月03日 统计字数: 6592字 阅读时间: 14分钟阅读 本文链接: https://soulteary.com/2017/07/03/improve-print-mode.html ----- # 优化网站打印(阅读)模式 作为一只深度“网瘾少年”,偶尔看到喜欢的网页内容,除了会选择使用笔记工具收藏、浏览器收藏夹库加star外,偶尔还会使用纸质打印保存,或者使用浏览器提供的网页转PDF功能留个备份。 然而提到打印,多数网页并不是都适合打印的。 ## 打印网页结果和传统书籍的异同 两者相同点挺多,但简而言之: > 基本都是图文混排的平面“图像”。 最关键的差异点: > 网页中呈现的内容和最后想打印的结果可能差异很大,屏幕尺寸和要打印的纸之间的比例不同,网页中有大量的不需要打印的元素,甚至如果直接在打印机上预览页面,页面脚本可能无法正确的被执行(前端模版方案包含的页面内容可能被忽略掉)。 ## 解决方案 - 优选使用CSS提供的@media规则来针对特定设备来展示内容。 - 次选使用JS来针对设备、屏幕宽度、UA来进行内容渲染和展示。 - 在实现页面内容呈现的过程中,HTML结构中最大程度只保留页面必要内容。 ## CSS Media Query 这个方案实际上是相对比较稳健的,IE从6.0支持`all, print, screen`这三种基本的屏幕类型的样式的加载(link tag),其他浏览器基本天然支持。 [MDN文档](https://developer.mozilla.org/en-US/docs/Web/CSS/@media)中对于Media Query的语法和特性描述已经很多,这种基础的事情就不赘述了,下面聊一下怎么去更好的定义页面内的样式。 以Bootstrap为例,在v3主流版本中,它的打印机样式,主要是依赖html5-boilerplate和自己的Print utilities中的声明来实现的。 - html5-boilerplate相关代码: [GitHub地址](https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css#L205) ```css /* ========================================================================== 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内容: ```less // 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的优化: ```css 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的优化: ```less @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元素进行隐藏。 划个重点细节: ```html ``` 如果你把打印机样式和其他的样式都打包成一个文件,请记得取消media的设置,或者设置为`all`,如果像上面一样设置为screen,那么之前的工作就白费了。 或者你也可以根据设备进行样式的拆分,将打印机样式单独提取到一个地方,比如print.css,在保持之前的样式不变的情况下,使用下面的结构。 ```html ``` ## 依赖JS的实现 参考[detecting-print-requests-with-javascript](https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/)的特性检测的方式,很容易包装一个你自己小模块。 不过这里除了可以用JS追踪用户是否进行了打印操作外,还可以稳定的添加当前设备模式的类名来给CSS进行操作举个简陋的例子: ```js 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了,偶尔可能会有点击按钮,调出系统对话框进行打印的需求。 常规做法是: ```js document.queryCommandSupported("print") && document.execCommand("print", false, null) ``` 非常规做法是: ```js focus(), print() ``` 如果在2017年你还要考虑IE678的话,那么可能你需要给页面添加一个iframe,再进行`window.print()`的调用了。 ## HTML结构优化 这个其实没有什么可说的了,记忆中有一篇老博客对这块有具体的描述,简单思路来说就是页面只展示基础的文章内容,任何修饰部分都交给JS来处理。 这样,当JS无法运行,或者运行会被阻塞的时候,页面所提供的内容依旧是完整的,并且可以大幅简化可运行JS设备的程序的复杂度,提升可维护性以及执行效率。 ## 结束语 这套组合拳打完之后,基本你的网站就拥有了和Safari阅读模式一样的体验了,不论是直接阅读,还是打印阅读,相比较之前体验应该是有质的提升的。(可以用Safari体验一下本站 :D ) ## 其他 - 需要在修正网站后,补充图片对比。 - 需要找回之前对于HTML页面结构优化的思考的文章链接。 --EOF