前端面对针式打印机打印局部内容解决方案

99 阅读4分钟

故事讲解

最近甲方提出了个需求,想要实现打印票据,我寻思这种小事情,不是刷刷刷的解决,立马就想到浏览器自带的打印API:print(),刷刷刷,一小会就写完了,自信提交上传,甲方立马打回,对着我说:“我只要打印页面中的一小部分,你这方法把我整个页面打印出来了!!!”。

我左思右想,查查网上的攻略,又看看网上的第三方库,我发现网上的解决方法都不能很好的适配我的项目,例如使用第三方库:print-js、vue3-print-nb这些第三方库。

但是这些第三方库他们的原理大概是这样的,他们是获取页面上的css,紧接着深度克隆dom然后将这些内容渲染到一个新页面,在调用打印全局功能。

但是这个CSS获取他是获取到页面请求的 link标签,以及页面上写的style标签,拿到写出来的样式在进行打印。欸欸欸,这就是一个很严重的问题了,这获取的CSS在全局层面肯定是有用的,但是,现在是要把小块内容给单独拿出来,这问题就很严重了,如果写的样式是被父组件控制的样式,或者别的页面使用 :deep 修改了就会出现打印的效果和浏览器上的效果不一致。如果修改样式,还需要我额外配置样式,这太麻烦了,不是我喜欢的。

既然使用打印第三方库有问题,那我试试其他方法吧。突然脑子灵光一闪,既然样式有问题,那我就转成图片吧,转成图片在打印,就不能出现样式问题吧,说干就干,先使用html2canvas转成图片(如果有同学发现html2canvas使用的时候,颜色没对上及其他问题,将缩放率改成 1.5 有奇效)。我将页面局部转成html2canvas在单独打印这一张图片,太简单了,直接完成上交。

但是没过多久,甲方打电话过来,喂喂喂,你这打印效果不是很好啊,怎么颜色这么淡啊。我顿时愣住,什么这是什么情况,咋还这样,询问甲方原因,后面才知道,甲方的打印机是针式打印机,针式打印机打印图片效果是非常差。

为了解决这个问题,苦思冥想,在上厕所的时候茅塞顿开,既然渲染前获取的样式有问题,不能获取全,那我就获取渲染完成之后的样式咯,根据浏览器的渲染原理和机制,直接拿取浏览器渲染完成的样式和dom不就行了,说干就干

代码编写

为了阐述市面上的打印解决方案,根据自身经历改编,写成了上述的故事,接下来是代码的编写

获取样式

首先得获取指定区域渲染完成之后的样式

/**
 * 精确克隆元素及其计算后的样式
 * 深度克隆一个元素,并将所有原始元素的最终样式应用为克隆元素的内联样式。
 * @param {HTMLElement} element - 要克隆的原始元素。
 * @returns {HTMLElement} - 一个带有完整内联样式的全新克隆元素。
 */
function cloneWithComputedStyles(element) {
  const clone = element.cloneNode(true)
  const originalElements = [element, ...element.querySelectorAll('*')]
  const clonedElements = [clone, ...clone.querySelectorAll('*')]

  originalElements.forEach((originalEl, i) => {
    const computedStyle = window.getComputedStyle(originalEl)
    const clonedEl = clonedElements[i]
    for (const prop of computedStyle) {
      clonedEl.style[prop] = computedStyle.getPropertyValue(prop)
    }
  })

  return clone
}

上述代码是获取指定元素的所有元素及样式,直接使用此函数即可,无需二次修改 调用时只需传递需要克隆的元素,如:cloneWithComputedStyles(document.documentElement) 打印之前在创建一个新的页面:

  const printWindow = window.open('', '_blank') // '_blank'新开 _self当前

新开完成之后,在注入需要打印的内容及初始化页面

  // 算了不想写注释了,自己将就看吧,不行就给AI看
  const styledClone = cloneWithComputedStyles(这里替换成你需要克隆的元素)
  // 设置打印内容渲染进body内
  printWindow.document.body.innerHTML = styledClone.outerHTML
  // 设置打印标题
  printWindow.document.head.innerHTML = '<title>打印预览</title>'
  const printStyle = printWindow.document.createElement('style')
  printStyle.textContent = `body { margin: 0px; }`
  printWindow.document.head.appendChild(printStyle)
  printWindow.document.close()
  setTimeout(() => {
    printWindow.print()
  }, 250)
  printWindow.onafterprint = () => {
    printWindow.close()
  }

差不多组装完成,就可以使用了,这个是使用原生的方法实现,所以完全适配 vue、原生、uniapp、react,目前我比较懒,没有做成第三方库,如果有需要,可以在评论区留言,我这给大家制作一份,方便使用。 注:有问题的朋友在下方留言,看到了我会回复。