由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。


👉 个人博客主页 👈
📝 一个努力学习的程序猿


其他前端组件使用和踩坑记录文章,欢迎您查看:



介绍

1、html2canvas

该插件允许我们直接在浏览器上对网页或其部分进行“截图”操作,但是屏幕截图基于 DOM,这一点很关键。这就会导致截图的结果,很可能不是自己想要的。而且也会受到 CSS 的影响。在后续的使用中就可以发现这一问题,而且很有可能导致 bug 的发生(本文中就记录了一个)。

而它的优势就是不需要来自服务器的任何渲染,因为整个图像是在浏览器上生成的。但是,也正因为它严重依赖浏览器,所以这个库不适合在 nodejs 中使用。它也会受到浏览器策略的限制,因此呈现跨域内容也需要代理来解决
在这里插入图片描述


2、jspdf

该插件就是用来生成 PDF 的。
在这里插入图片描述


在使用时,利用 html2canvas 将页面转换成图片,然后再通过 jspdf 将图片的 base64 生成 pdf 文件。

而为什么要将生成 pdf 分成两步,使用两个插件?

【关于更详细的使用内容,为了避免大篇幅引用,您可以直接前往原链接中查阅,本文后续主要针对遇到的问题】

首先只用 jspdf 生成 pdf 的话,用法有些繁琐。感兴趣的话,可以查看官方demo的使用方式:
npmjs.com/package/jspdfjsPDF Demos

其次 jspdf 不支持中文。如果您真想直接使用 jspdf,可以查看该文章:jspdf简单使用

除此以外,最关键的就是很难保持原有页面的样式。这样生成的 pdf 可能既不美观,也不满足要求,而且工作量还很大。

因此,最快捷简便的办法就是先使用 html2canvas 将页面转换成图片(html2canvas 使用方法),然后再将图片导成 pdf。用法很简单。使用方法在下面附上原文链接。


使用方法

在 Vue 项目中的使用方法,为了避免大篇幅引用,您可以直接前往以下文章查阅:前端实现HTML转PDF下载的方式 。经测试完全可用。在这里将不做复制粘贴。但是这个方式下,会导致一些其他的问题。接下来将对这些问题进行总结。


问题一、分页分割问题

如果使用上述方案,那么当页面长度超过指定的高度(默认是 a4),这时页面的内容很可能会出现被分页分开,而导致页面内容分割的情况。如下图所示。

在这里插入图片描述
【图片截取自:jsPDF 分页分割问题踩坑

在网上找了很久的答案,似乎大家都会被这样的问题所困扰。解决方案大致也就这几种,总结如下:

1、页面设计的时候就考虑到分页的情况,通过 CSS 设置高度来避免页面被分割。当然这样工作量就很大了。

2、只使用 jspdf。如果各位想这么做,当然也可以,工作量也会很大,在前文也有相关表述,附上了原文链接。

3、如果没有特殊需求,最优解就是不分页。这样就能保留页面内容,而且也不会出现问题。但是一个最致命的缺陷也就在这里,pdf 要是不分页的话,打印机打出来得多长一张纸?

所以,从目前来看,如果没有打印等特殊需求,可以使用不分页的情况,不费时不费力。如果需要分页打印,那只能考虑方法1了。如果您有更好的方法,欢迎在评论区留言。

关于 方法1 + 方法3 更详细的说明,为了避免大篇幅引用,您可以直接前往以下文章查阅:jsPDF 分页分割问题踩坑

在这里为了方便查阅,简单粘贴一下 方法3 实现代码:

htmlToPdf.js:

// 导出页面为PDF格式
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
export default {
  install(Vue, options) {
    Vue.prototype.getPdf = function() {
      const title = this.htmlTitle;
      const scale = 2;
      html2Canvas(document.querySelector('#pdfDom'), {
        allowTaint: true, // 开启跨域
        scale // 提升画面质量,但是会增加文件大小
      }).then(function(canvas) {
        const contentWidth = canvas.width / scale;
        const contentHeight = canvas.height / scale;
        const PDF = new JsPDF('', 'pt', [contentWidth, contentHeight]);
        const pageData = canvas.toDataURL('image/jpeg', 1.0);
        PDF.addImage(pageData, 'JPEG', 0, 0, contentWidth, contentHeight);
        PDF.save(title + '.pdf');
      })
    }
  }
}

main.js:

import htmlToPdf from '@/utils/htmlToPdf';
Vue.use(htmlToPdf);

html.vue:

<div id="pdfDom">
   <!-- 要下载的HTML页面 -->
  <div>内容</div>
</div>
<el-button type="primary" size="small" @click="getPdf('#pdfDom')">点击下载</el-button>
export default {
  data () {
    return {
      htmlTitle: '页面导出PDF文件名'
    }
  }
}

问题二、html2canvas生成图片只有一半

首先先来看一下出现问题的效果:
在这里插入图片描述
可以发现生成的 pdf 文件中,目前只生成了右半部分的内容。对于问题的锁定也是很快的,问题只能出现在 html2canvas,也就是截图操作上。在前文有提到,html2canvas 基于 DOM,且会受到 CSS 的影响,那么问题其实也是出在这个方向上。

在网上查询相关解决方案时,发现所有方法都无法解决自己的问题。为了避免大篇幅引用,各位如有需要,可以前往这些文章查看,看看是否能解决问题

html2canvas 生成图片 生成一半有图一半空白 是什么原因导致

html2canvas生成的图片偏移不完整的解决方法

html2canvas,则只绘制图像的一半或不绘制某些图像

html2canvas (踩坑) 网络图片显示不出来&生成图片只有一半或者空白&文字显示不出来问题处理

而经过网上冲浪一波,最后总结下来的结论就是以下几点:

1、首先查看需要生成图片的 DOM 结构,是否渲染完成。

2、如果渲染完成,再查看是否由于页面内容超出了 宽 / 高 而被隐藏导致。(比如受到了 CSS overflow 的影响,即出现滚滑轮)

3、比较玄学的就是在触发生成图片后,不要滚动页面。(没这个操作请忽略)

4、再玄学点就是调整 html2canvas 版本,听闻可以把 html2canvas 版本切换到 1.0.0-rc.4 尝试。

不过以上方法都没有解决自己的问题。那么问题到底出现在哪里?

其实在寻找解决方法的过程中,已经可以发现一些端倪。打印出图片的效果,很有可能是受到了 CSS 的影响。果然,网上也有提到:ios部分元素丢失、部分css3属性不支持、图片使用 img 标签不使用 background-img 、图片跨域问题等等。


所以从 CSS 入手果然发现了问题。因为我只打印出了右半部分内容,所以该问题大概率出现在我外侧包裹的元素上。比如,我是这样写的:

<el-card id="pdfDom" class="cardPdf">
	<!-- 内容 -->
</el-card>

对于 el-card 的样式就放在了 cardPdf 上。其中,由于之前提到分页分割问题,所以这里我其实是指定宽度高度去生成这样一张图片。那么在不同分辨率的页面下,就肯定无法做到居中处理。所以为了居中,我加了这样一段 CSS:

>>> .cardPdf {
  width: 1291px;
  padding: 20px 30px;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}

而问题就碰巧出现在了居中的这段 CSS 上。

由于在最开始没把问题归结到 CSS 上,所以花费了很长时间。最终在同事的提醒下,发现了这个问题。从源码中可以发现:html2canvas 它支持的 css transform 只有 2d 和 3d 的矩阵变换。

那为了解决这个问题去居中,就可以使用这样的方式:

>>> .cardPdf {
  width: 1291px;
  padding: 20px 30px;
  margin: 0 auto;
}

最终发现问题解决:

在这里插入图片描述

所以,如果各位也有部分内容展示不出来的情况,就可以从这些元素入手。看是不是因为某个 CSS 属性不支持 又或者 是因为某个标签不支持。由于不支持的要素可能比较多,也很难总结,出现问题也就只能一点一点排查了。


问题三、pdfjs 生成图片大小异常

最近在使用过程中又遇到了展示不完整的情况,可以很明显的看到右侧文字内容未展示完全:

在这里插入图片描述
根据之前的道理,首先把问题锁定在 html2canvas 这一步上,怀疑生成的图片就是残缺的,即 CSS 问题。所以我去掉了所有的 CSS,变成纯文本后,输出查看 base64 时发现,此时图片是完整的。

在这里插入图片描述

那么也就说明,问题不是 html2canvas,这回是 jspdf 出现了问题。

代码使用的是上面页面分割解决方案的代码(这次没注册成全局变量,而选择了局部引用)。

// 导出页面为PDF格式
import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
export const getPdf = (id, title) => {
    const scale = 2;
    html2Canvas(document.querySelector(id), {
        allowTaint: true, // 开启跨域
        scale // 提升画面质量,但是会增加文件大小
    }).then(function(canvas) {
        const contentWidth = canvas.width / scale;
        const contentHeight = canvas.height / scale;
        const PDF = new JsPDF('', 'pt', [contentWidth, contentHeight]);
        console.log('content', contentWidth, contentHeight);
        const pageData = canvas.toDataURL('image/jpeg', 1.0);
        PDF.addImage(pageData, 'JPEG', 0, 0, contentWidth, contentHeight);
        PDF.save(title + '.pdf');
    })
}
const test = document.getElementById('pdfDom');
console.log('元素大小', test.clientWidth, test.clientHeight);

通过输出 contentWidth、contentHeight、元素的宽高,结果发现相距不大。
在这里插入图片描述
因此,既然宽高没错,那问题只能出在 addImage 的坐标设定和绘制上了。

果不其然,通过调整其中的参数就可以解决问题(参数如何确定暂时未知,目前是手动测试发现 1.5 合适。后来发现,在不同的页面,该值的大小将不是个确切数字)。

PDF.addImage(pageData, 'JPEG', 0, 0, contentWidth / 1.5, contentHeight / 1.5);

如果各位有类似的问题,也可根据上述思路进行测试。但目前还不清楚为什么需要再有个除数以及该除数的比例该如何计算,而为什么在上文解决页面分割时没遇到此问题。如果各位能够答疑解惑,也欢迎在评论区提问。


其他参考文章

在查询解决方法的过程中,也发现一些有用的文章,如果后续遇到类似问题,也可以前往查看:

使用html2canvas在前端生成图片及相关bug问题整理


由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。


👉 个人博客主页 👈
📝 一个努力学习的程序猿


其他前端组件使用和踩坑记录文章,欢迎您查看:

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐