故事讲解
最近甲方提出了个需求,想要实现打印票据,我寻思这种小事情,不是刷刷刷的解决,立马就想到浏览器自带的打印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,目前我比较懒,没有做成第三方库,如果有需要,可以在评论区留言,我这给大家制作一份,方便使用。 注:有问题的朋友在下方留言,看到了我会回复。