在浏览器上打印应该一个比较常见的操作。
最简单的打印方式就是直接点击浏览器右上角,找到“打印”按钮或者调用window.print()
。
这就能将当前页面整个打印出来了。
只是,很多情况下需求都不会如此简单,更多的可能是需要打印页面中的某一段“特定”的内容或者自定义一段内容。
这就需要用到自定义打印了。
一、自定义打印两个方法
实现自定义打印的方法网上能找到很多,有各种不同的实现方案和 js 库。
其中有两个比较常用和简单的方法:
1)直接调用window.print()
。
在调用此方法之前将不需要被打印的元素先通过display='none'
隐藏掉,打印执行完毕h后再通过display='block'
还原显示。
2)创建临时 iframe 进行打印。
创建一个临时的 Iframe 标签,后将需要打印的内容拼接成 html 字符串渲染到 Iframe 里面,最后执行iframe.contentWindow.print()
。
方法 1 操作起来方便快捷适合简单的页面,对于稍微复杂一点的页面就很不方便了。
方法 2 适合复杂的打印需求,几乎可以满足所有的打印需求,本人设计的自定义打印方案也是基于此方案的。
1.iframe 打印基本使用
Iframe 打印最终也是调用 window.print()
的。
只不过我们将需要打印的内容渲染在 iframe 内部,后再 iframe 内部调用打印。
打印方法实现如下:
1 | /** |
html 字符串拼接方法实现如下:
1 | /** |
至此,一个基本的打印功能就完成了,针对单页打印、普通文本的打印已经足够用。
只是,这就结束了吗?
当然不会,实际需求中还有更复杂的打印场景,比如当打印报表。
打印报表的时候往往会涉及到分页、页头、页眉、页脚等比较复杂的元素。
甚至还有一些合理但是比较复杂的要求。
比如:
1 | 第一页需要页头其它也不需要 |
很显然,面对这些“有理”要求,仅靠上面这个方案还做不到。
二、定制化的自定义打印
上文实现的打印,其实现原理无非是拼接 html 字符串,然后将字符串传入 iframe,然后进行打印。
而作为一名前端开发,操作 html 就像呼吸一样简单,想要在网页上画出来分页、表头、页眉、页脚这些根本没什么难度可言。
因此,理论上只需要在原方案基础上做“亿点优化”就可以解决了。
下面介绍一下本人的设计实现方案:
具体打印方案
首先从接口拿到数据并将其转换成下面的数据结构。
其核心为 pageList
,这个 pageList 保存的就是打印的时候每页用到的数据和相关配置。
我们为每一页定制配置当页渲染所需要的特定的数据。
比如只有第一页有页头、每一页都自己的页码。
1)约定的数据格式示例
1 | const data = { |
这分数据属于是定制化数据,具体数据格式和需要打印的 html 模板文件有关。
每一页的数据都是通过手动计算出来的,计算方法示例如下:
1 | /** |
不难看出,上述方法最终输出的是一个大的 pageList, 内部有一个小的 list。
pageList 包含的是各个页面的数据,而 list 包含的是某一页的列表数据。
除此之外,还有当前页面的页码,是否应该包含头部信息等。
这份数据就是为分页服务的,有了这份数据,我们只需要同步设计出相应的 html 模板.
然后将对应的数据传入模板进行渲染就能得到相应的分页 html 字符串了。
2)对应的 html 模板
html模板可以是任何模板语法,这里我们采用的最简单的mustache
语法
1 | <body class="a4-body"> |
不难看出,当我们将前面格式化出来的 pageList 数据渲染到如上模板就能得到多个 pageList。
每个 pageList 又包含多个数据行,最终输出的就是一个分页的 html 字符串结构了。
当然,仅仅有对应的结构是不够的,作为 html 页面,还需要配合对应的 css 样式。
所以,我们还需要用 css 来做一些布局来保证 pageList 里面的一个 item 的总高度为 A4 的高度
。
只要保证这个高度,其内部样式如何变化都没关系,多一个 header、或者某个特殊页面多一个特殊元素都无所谓。
无非是在计算 pageList 的时候对数据进行增减即可。
因此,根据上文的 html 模板,对模板里面的元素设置其 body 容器和 page 容器的高度,使其每一页高度固定。
这样我们打印出来的内容就是我们最终期望的分页数据了。
CSS 核心代码如下:
1 | /* css全部使用mm作为单位 */ |
至此,有了 html 模板和 css 负责处理 ui 和布局,传入分割好的数据,最终就能渲染出固定样式的 html 页面内容了。
后面不论需要打印的内容如何变化,只需要处理好这几部分之间的关系,我们就能得到对应的 html 页面,将其塞入 iframe 就能打印任意内容。
三、静默打印
前面我们都是调用的浏览器自带的打印能力,即 window.print()
方法触发的浏览器预览打印。
这种方式非常简单,接入也不麻烦。
然而,它有一个不容疏忽的缺点(也不算缺点,毕竟浏览器并不是专业打印设备,需要考虑到安全性和通用性),那就是打印触发之前它一定会弹出一个“预览”大弹窗。
而有时候我们的需求是点击按钮就实现打印,直接给打印机发出打印指令,不要弹出打印“预览”弹窗。
通过各种途径了解到,这是无法实现的,至少纯“浏览器前端”,通过浏览器端的 js 无法实现。
那就没有办法了吗?
当然有,那就是自己开发一个打印App
。
浏览器本身其实也可以看做是一个特殊的“打印App”。
浏览器能调用打印机,自定义打印App当然可以。
1、如何设计打印App的功能
打印App就一个PC端的应用,用 Electron 就能很轻松的做出来。
其需要实现两个核心功能:
1 | 1.连接和管理电脑设备上的打印机 |
连接和管理电脑设备上的打印机这个这里不做详细介绍,网上有成熟的第三方库来实现。
至于如何与浏览器进行通信,这里简单介绍下实现思路。
其实也很简单,无非就是 socket 通信。
我们只需要在此应用上启用一个 Socket Server 服务。
这个 Socket 服务和我们服务器上启动的服务是一样的,只不过此服务是直接部署到我们用户的本地机器上的,只给当前用户使用的。
此服务监听一个端口,比如:18877。
之后我们只需要在浏览器端启动一个 Websocket 本地客户端,然后建立与 ws://127.0.0.1:18877
的连接即可。
当我们需要打印的时候,只需发送socket 信息给打印 App,将打印事件、打印文本及其他相关打印信息发送给打印控件服务。
打印控件接收到请求之后再调用电脑的打印功能,调用打印机即可。
至此,一整套打印控件打印方案就算完成了。