作为一只深度“网瘾少年”,偶尔看到喜欢的网页内容,除了会选择使用笔记工具收藏、浏览器收藏夹库加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中的声明来实现的。
- html5-boilerplate相关代码: GitHub地址
/* ==========================================================================
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