likes
comments
collection
share

关于vue-print-nb这个库的一些思考

作者站长头像
站长
· 阅读数 14

前言

打印是前端挺常见的一个需求,整个页面打印只需要执行 window.print() 即可。而页面的局部打印就比较麻烦了,需要借助iframe实现。我目前在 github 上找到比较好用的vue相关的是 vue-print-nb 这个库。公司内部也是使用的这个,但是最近在开发一个需求的时候在chrome49上遇到一个bug,引发了一些我思考。

:下面说到的bug仅在chrome49上出现,高版本没有问题。

遇到的bug

首先交代一下遇到了什么bug吧。我在开发一个报告弹窗相关的组件,其中需要实现打印报告的功能,使用的就是 vue-print-nb 这个库。之前其实也使用过很多次,一直都没啥问题,但是这次却出现了无法弹出打印窗口的问题。我一开始以为是我封装组件的问题,因为在同样的开发环境下,之前开发的功能可以打印,而新的组件无法打印。但是经过调试后发现其实是已经执行到 vue-print-nb 的库的代码,具体如下图:

关于vue-print-nb这个库的一些思考

与之前可以打印功能不同的是,这个组件的打印执行了 print 函数后,并没有执行 load 事件的回调,所以也就根本没有执行 iframeWin.print()

那么为什么同样的开发环境,同样的chrome49,之前开发的功能是正常的,而这次开发的组件就无法打印了,我仔细去阅读了 vue-print-nb 相关的源码,并产生了一些疑问。

产生的疑问

  1. document.body.appendChild(iframe) 后才对 iframe 进行 load 事件的绑定会不会产生问题?

    源码中在 document.body.appendChild(iframe) 后又执行了很多代码,最后才进行的 load 事件的绑定(感兴趣的小伙伴可以自行阅读源码,这里就不多做介绍了); 其实大部分情况都是不会有问题的,因为 load 事件是需要等待 iframe 中内容全部加载完成才会执行,尤其是一些图片等加载是比较慢的,而代码的执行速度是很快的。

  2. 为什么在 load 事件时才执行打印,而不是直接执行打印

    看源码可知 document.body.appendChild(iframe) 执行的很早,那为什么还要把 iframeWin.print() 放到 load 回调函数内执行,而不是直接执行呢?是因为需要保证 iframe 内容(尤其是图片)加载完成再在执行打印,否则可能会出现打印窗口内图片无法显示或者只显示一半的情况。

  3. 为什么不在 print 函数中 load 事件绑定后执行 document.body.appendChild(iframe)

    因为仅通过 createElement 创建出来的 iframe 是不具有浏览上下文(browsing context)的,也就是没有 iframe.contentWindowiframe.contentDocument,那也就意味着无法通过 iframe.contentDocument.write 方法将需要打印的内容写入到iframe了。

产生bug的原因

说了那么多,那到底是什么原因产生了这个bug呢?请回看上述疑问第一条中提到:大部分情况都是不会有问题的;那么什么情况下会有问题呢?就是当打印内容很少或者全部都是文本时,iframe 很快就能够 load 完成,那么等到 print 函数中执行 load 事件绑定时,其实 iframe 已经 load 完成,这就会产生无法弹出打印窗口的bug(即没有执行 iframeWin.print())。

解决方案

解决这个问题的思路就是在 print 函数中做判断,判断如果 iframe 已经 load 完成,那么我们应该直接执行 _loaded 函数;如果 iframe 还没 load 完成那么再进行 load 事件的绑定。那么该如何判断 iframe 是否已经 load 完成呢?思路就是在 document.body.appendChild(iframe) 前就先绑定一个 load 事件回调,伪代码如下:

let iframeLoaded = false

// ...
iframe.addEventListener('load', () => {
    iframeLoaded = true
})
document.body.appendChild(iframe)

// ...

// print函数内
// ...
const _loaded =() => {
    // ...
    iframeWin.print()
    iframeLoaded = true
}

if (iframeLoaded) {
    _loaded()
} else {
    iframe.addEventListener('load', _loaded)
}

总结

本文主要讲述了发现bug、阅读源码、分析原因、提出解决方案的一系列思路。同时也没发现关于打印的库比较少,我自己也准备着手写一个比较完善、支持ts的打印库。

转载自:https://juejin.cn/post/7177392882884542525
评论
请登录