浏览器打印的两种实践方案

语言: CN / TW / HK

1.浏览器自身打印

  • 使用 window.print() 调起浏览器自带的打印预览弹框打印
  • 默认会打印 body 里面所有内容

js const handlePrintPdf = () => { window.print(); }

如果想要局部打印可以使用 CSS 媒体查询,甚至还可以自定义打印时的样式,这些样式只会在打印的时候生效

```html

```

可使用媒体查询在打印的时候去除页眉页脚,设置打印布局等操作

```css @media print { @page { / 去除页眉 / / margin-top: 0; / / 去除页脚 / / margin-bottom: 0; / / 去掉页眉和页脚 / margin: 0; / 纵向 / size: portrait; / 横向 A4 / size:A4 landscape; }

  body {
    margin: 1cm;
  }

}

```

如果内容固定且具有分页,浏览器自身分页可能会和预想中情况不一样,我们可以使用 CSS 进行强制分页

css @media print { /* 在main元素前始终插入分页符,强制使其分到下一页 */ main { page-break-before: always; } /* 在footer元素后始终插入分页符 */ footer { page-break-after: always; } }

打印部分区域的内容还可以这样做

动态创建一个不可见的 iframe, 将需要打印的 dom 节点插入 iframe 内,并调用 iframeprint 方法

js printBtn.addEventListener("click", () => { const printContentHtml = document.getElementById("print").innerHTML; const iframe = document.createElement("iframe"); iframe.setAttribute( "style", "position:absolute;width:0px;height:0px;left:-500px;top:-500px;" ); document.body.appendChild(iframe); iframe.contentDocument.write(printContentHtml); iframe.contentDocument.close(); iframe.contentWindow.print(); document.body.removeChild(iframe); })

或者在新打开的页面打印

js printBtn.addEventListener("click", () => { const printContentHtml = document.getElementById("print").innerHTML; const printPage = window.open(); printPage.document.write(printContentHtml); printPage.document.close(); printPage.print(); printPage.close(); })

当然我们也可以这样,先获取需要打印的 dom 节点,替换当前 body 下的节点。完成打印后恢复 body 下的节点

```html

段落1

段落2

段落3

```

这样的话打印的时候页面原有元素会丢失,并且这时可能图片也未加载完成,很多元素宽度也会有问题要调整,我们可以更进一步封装方便修改,整体代码如下:

```html

段落1~

段落2~

段落3~

```

很可惜此类方案和很多第三方库会有元素丢失和样式错乱的情况,不尽如人意

此时一个方案浮出水面

2.html2canvas和jspdf

  • 简单说就是将网页通过 html2canvasdom-to-image也可以) 转换为图片,再由 jspdf 生成该图片的 pdf
  • 此方案也会有一些问题,例如 iframe 嵌套(项目中富文本编辑器会用到)的内容会无法打印,无法复制 pdf 上的文字等内容,而且清晰度可能会存在问题
  • 图片输出 pdf 的时候根据宽高裁切,分页的时候可能会有内容生生的被截断,需要根据不同项目特殊处理(逻辑复杂)

先来说说这两者基本使用

安装:

shell yarn add html2canvas jspdf

使用 html2canvas 对页面截图

js // allowTaint 是否允许跨域加载图片 // useCORS 是否使用CORS从服务器加载图片 // scale 渲染像素比率,默认当前设备像素比 printBtn.addEventListener("click", () => { html2Canvas(wrap, { allowTaint: true, useCORS: true, scale: 2 }).then( (canvas) => { document.body.appendChild(canvas); // 转换canvase为base64图片,第二个参数1代表图片质量(0-1) canvas.toDataURL("image/jpeg", 1); } ); });

此方法无法打印 iframe 里面的内容,于是我遍历整个 iframedom 替换,此外我们还要书写逻辑手动对图片指定宽高进行截图分页,此部分可以将其封装下方便复用,具体方案如下

```html

```

注意打印完之后最好在顶层组件封装个 reload 方法关联 v-if 通过 provide 提供下去,组件调用下重新渲染一遍(因为之前我们替换了页面元素,不刷新还原可能会有问题)

js reload() { this.isRouteAlive = false; this.$nextTick(() => { this.isRouteAlive = true; }); },

最后这个截断问题比较棘手,我们可以这样解决

  • 设置对应转换后的 canvas 为白色背景
  • 转换图片后获取对应截断处的图片像素
  • 从截断处一行行往上扫描像素点颜色
  • 碰到这一行是全白的,代表从这里截断,将这个高度往下的内容放到下一页

具体代码如下!

```html

```