最近业务中有需要用到浏览器打印功能,本来以为很简单,但实操下来并非一帆风顺,想要达到预期的效果有不少阻碍,因此记录一下。
基础简介
- 核心方法
window.print()
- 调用后会弹出打印预览页面,并使浏览器进程进入等待状态,点击打印或者取消按钮后才会继续运行后面的代码
- 打印设置建议设置 无边距,方便控制打印效果,第一次设置后,浏览器会保存设置
- 打印纸张尺寸其实是一个比例,入A4纸为 根号二比一,只要控制好最外层的比例打印基本就不会有问题
几种浏览器打印的方法
不想看废话的直接看最后一种方法,通过iframe在页面视觉上无任何变化的情况下触发打印
整页打印
- 通过
window.print()
直接整页打印- 会打印当前整个
window.document
对应的页面 - 这种方法最简单,也最没用,基本无法实现各种业务的打印场景
- 会打印当前整个
打印自定义的组件
- 自定义组件最好使用内联样式
-
通过
window.print()
打印自定义的组件- 先获取自定义组件内容元素
- 保存当前页
body
下的整个html
- 将自定义组件元素替换
body
下的元素 - 执行打印
- 打印完成后还原原来的页面
- 这种方法会将当前页面替换成打印页内容,视觉上会有变化,某些场景可能不适用
-
通过
window.open()
打印自定义的组件- 先获取自定义组件内容元素,该组件可以使用
z-index: -9999; opacity: 0;
等方式不在原页面上显示 - 使用
let newWindow = window.open("",'newwindow', 'height=300, width=700, top=100, left=100, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no');
打开一个新窗口 - 将自定义组件元素内容放入新窗口的
body
中 - 执行打印
- 打印后执行
newWindow.close()
关闭新窗口 - 这种方法可以做到不改变原页面视觉效果,但是会弹出新窗口,某些场景可能不适用
- 先获取自定义组件内容元素,该组件可以使用
-
通过
后端URL
打印自定义的组件- 从后端拿到自定义组件内容的
URL
请求路径 - 使用
let newWindow = window.open(URL,'newwindow', 'height=300, width=700, top=100, left=100, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no');
打开一个新窗口 - 执行打印
- 打印后执行
newWindow.close()
关闭新窗口 - 这种方法与上一种方法大同小异,只是获取自定义组件内容元素从前端移到了后端,缺点同样存在
- 从后端拿到自定义组件内容的
-
通过
iframe
打印自定义组件- 先获取自定义组件内容元素,该组件可以使用
z-index: -9999; opacity: 0;
等方式不在原页面上显示 - 添加
iframe
元素,该元素可以使用display: none;
等方式不在原页面显示 - 将自定义组件元素内容放入
iframe
的body
中 - 执行打印
- 这种方法应该是最接近预期的,在页面视觉上无任何变化的情况下触发打印,但是也有几个问题要注意一下
- 先获取自定义组件内容元素,该组件可以使用
iframe打印要点说明
iframe默认样式覆盖
iframe
相当于一个新窗口,有内置自带的样式,而通常现在工程化的页面中会使用reset.css
等手段拉齐各浏览器的样式,所以在原页面定义的组件放入iframe
中可能表现不一致,因此需要将iframe
问题样式覆盖掉- 比较常见的问题样式(比较影响打印效果的,以谷歌为例):
body
自带的margin: 8px;
,需要将外边距置为 0- 盒模型问题,需要在带
padding
的元素上加上box-sizing: border-box;
- 包括
line-height
等样式以及h1、h2、p
等元素,注意覆盖样式或者避免使用
- 也可以将原页面
<head>
中的内容全部放入iframe
的<head>
中:iframe.document.head.innerHTML = document.head.innerHTML;
,此法某些场景不适用
高度和宽度最好固定
- 利用响应式布局写的组件,通常不用使用固定宽度,但是发现火狐浏览器(不排除其他浏览器)带了
18px
的边距(即使设置了无边距),这样会导致火狐样式错乱,此时需要设置固定宽度 - 如
A4
纸打印,可设置高度为1122px
,宽度为793.37px
(根据固定比例 根号二比一 计算得出)
打印分页
- 就是用到了
css
中的一个属性,但是网上很多相关内容过于陈旧,找到正确的属性费了点时间。 - 亲测有效,
break-after: page;
打印时会在设置了该属性的元素之后进行分页
防止分页
- 避免在元素内部插入分页符,比如图片被截断
page-break-inside: avoid;
加上该属性后,如果当前页放不下,内容会进入下一页。而不会从中间截断
参考代码示例(vue)
<div class="cover-preview" v-if="showPreviewCover">
<div id="print-cover-box" style="width: 793.37px;color: #000;">
<!-- 火狐带了18px的边距,即使设置为无边距 把宽度定死-->
<div v-for="item in coverList" :key="item.objectId" style="break-after: page;width: 100%; height: 1122px; padding: 140px 80px;box-sizing: border-box;">
<div style="width: 100%; height: 100%; border: 6px solid #000;padding: 6px;box-sizing: border-box;">
<!-- ... -->
</div>
</div>
</div>
</div>
<iframe id="print-cover-iframe" frameborder="0" style="display: none;"></iframe>
this.showPreviewCover = true;
this.$nextTick(() => {
let iframe = document.getElementById('print-cover-iframe').contentWindow;
var printHtml = document.getElementById("print-cover-box").outerHTML;
// iframe.document.head.innerHTML = document.head.innerHTML;
iframe.document.body.style.margin = '0px';
iframe.document.body.innerHTML = printHtml;
iframe.print();
this.showPreviewCover = false;
})
.cover-preview{
position: absolute;
left: 50%;
top: 0;
overflow: auto;
transform: translateX(-50%);
background-color: white;
border-left: 1px solid #333;
border-right: 1px solid #333;
z-index: -9999;
opacity: 0;
}