实现步骤:(只想了解vue实现的不用看方法一,直接看二)
方法1:
使用node.js和puppeteer(谷歌自动检测工具),由于第一种尝试结果不太理想,所以我直接粗略讲解:(原代码实现如下,依赖包并非所有都有用,puppeteer是必需要npm按照并且引入)
const puppeteer = require('puppeteer');
const useProxy = require('puppeteer-page-proxy');
const {delay} = require("bluebird");
const Promise = require("bluebird");
const ms = require("ms");
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch();
// const browser = await puppeteer.launch({headless:false});
// const page = await browser.newPage();
const page = await browser.newPage();
await page.goto('http://127.0.0.1:5173/',
{waitUntil:'networkidle2'}
);
// await delay(ms("5s"));
await page.pdf({path: './test123.pdf' , format: 'A4',printBackground:true});
await browser.close();
})();
实现原理很简单,就是通过puppeteer自动打开对于网页,然后调用他的pdf方法保存,值得注意的是这样printBackground:true这个参数控制的是背景颜色的显示,如果不选true,背景色是不会渲染出来的,这样做的好处是生成简单,图片非常清晰,即使放大后依旧清晰,并且不会有太大的内存,我这三张图片的原图就3m多,生成文件是3.56m可见比较符合预期的。缺点:①生成的pdf是默认两页,我尝试很多手段解决,可能是学术不精,我在puppeteer的API文档为找到对应的参数设置,百度搜索等方式也未找到好的解决方案,至今不清楚原因,我试了其他网站,有些一页就能显示完,具体原因我不详做叙述了,反正笔者可能是陷入死胡同,尝试很多无果后选择了vue去解决。②由于这样需要打开新的窗口,就比如会有跳转操作,虽然有无头模式,但是我未去尝试,因为已经被①劝退了。
方法2:
方法参考来源:
Vue页面生成PDF的方法_vue生成pdf_愤怒小绵羊的博客-CSDN博客
在vue的项目中,先安装需要用到的两个依赖分别是html2canvas和jspdf
①npm install --save html2canvas
②npm install jspdf --save
①的作用是将我们需要转换成PDF的html页页面先转换成canvas(canvas是html的一个标签,在画图、特效甚至小游戏方面非常重要,了解可以去看相关系列知识,不详解)转换成canvas之后我们可以通过一系列参数去调整我们需要转换的pdf,这里是非常重要的点,因为在默认情况下,生成的canva是非常模糊的,待会细讲问题及解决
②的作用就是将canvas转换成的图片转换成需要的pdf并且导出,这里能做的事情不多,也相对较为简单
接下来讲的是具体实现步骤:(参考博主愤怒的小绵羊,非常感谢分享,可以先根据小绵羊的代码来做,由于我的代码场景和他不同,我的并不是一个示例代码。可能会存在无法执行,但是绵羊的是可以的 我试过了。)
<template>
<button @click="handleExport">导出</button>
<div ref="pdf" class="spec">
需要转换成pdf的结构或者图片
</div>
</template>
然后定义点击函数,并且拿到对应页面的pdf传递给downloadPDF函数,由于downloadPDF结构比价多,将其抽离出来pdf.js里面保存
const handleExport =()=>{
console.log(proxy.$refs.pdf)
downloadPDF(proxy.$refs.pdf)
}
然后导入js,由于pdf.js内容较多就将其抽离了
import {downloadPDF} from "./pdf.js"
pdf.js内容如下:
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
import compress from './compress.js';
function base64ToFile(dataURL) {
var arr = dataURL?.split?.(',')
let mime = arr[0].match(/:(.*?);/)[1]
let bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let filename = new Date().getTime() + "" + Math.ceil(Math.random() * 100) + "." + mime.split("/")[1]
return (new File([u8arr], filename, { type: mime }))
}
export const downloadPDF = page => {
html2canvas(page,{
allowTaint: true, //开启跨域
useCORS: true,
scale: 2,
}).then(function(canvas) {
canvas2PDF(canvas);
});
};
const canvas2PDF = canvas => {
let contentWidth = canvas.width*0.2;
let contentHeight = canvas.height*0.2;
let imgHeight = contentHeight;
let imgWidth = contentWidth;
let pdf = new jsPDF("p", "pt");
let sharePic
sharePic = canvas.toDataURL("image/jpeg", 1)
let fileba = base64ToFile(sharePic)
compress(fileba)
.then(res => {
pdf.addImage(
res.compressBase64,
"JPEG",
0,
0,
imgWidth,
imgHeight
);
// console.log(pdf,999)
pdf.save("导出.pdf");
})
.catch(err => {
// error(err);
});
};
到这里就已经可以将对应的html代码转换成pdf了,接下来我将讲解我遇到的问题已经解决方案:
问题及解决:
①打印出的PDF没有图片部分
我遇到的第一个问题就是打印出的pdf没有图片,我试了在nodejs+puppeteer情况下是可以直接打印,我猜测可能是转换成pdf的方式不同,可能puppeteer类似于截图(猜测观点),而vue这种先转换成canvas的形式是需要下载图片资源的,所有下载资源就存在跨域问题,一开始我是想通过配置代理的方式实现(后续会详细讲解跨域问题以及配置代理方法,帮助自己复习总结并且发出来),不过结果并不是很顺利,也可能是我操作的问题,于是我通过搜索发现,可以将图片转换base64的格式就能避开跨域问题了,http://nomad-public.oss-cn-shanghai.aliyuncs.com/size_chart/34e4debd-a8ea-4730-acd2-8a58cc7c6b5d.jpg?time=1677729826618,我大致观察了一下好像就是加了个时间戳,然后在图片前加上了image.setAttribute("crossorigin", "anonymous");据说是解决跨域策略的方法,接下来我就把将图片转换成base64的方法贴出来:
const downloadImage = (imgsrc) => {//下载图片地址和图片名(下载部分代码已经被我删除了,这是上一个需求用到了,我在此基础魔改了一下)
var image = new Image();
// 解决跨域 Canvas 污染问题,
image.setAttribute("crossorigin", "anonymous");
image.onload = function () {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
var url = canvas.toDataURL("image/png"); //将图片格式转为base64
// console.log(url,"base64")
};
image.src = imgsrc + '?time=' + Date.now(); //注意,这里是灵魂,否则依旧会产生跨域问题
console.log(image.src,"image.src")
return image.src
}
这个函数的作用就是传入一个url,函数就会将转换base64格式的图片url返回了(可能概念会有错,因为我base64和canvas理解较浅),这样再去转换成pdf就会发现可以看到图片了,顺便小提一嘴,如果是背景图片的格式可以在template中用模板字符串以动态形式添加:style="{'margin-top':'0','background-image':`url(${downloadImage('https://nomad-public.oss-cn-shanghai.aliyuncs.com/size_chart/929c3c47-e0b6-4765-bda9-5fb8c1c05191.jpg')})`}",这样就可以避开设置动态css样式了,这样就解决了第一个问题,pdf中无法显示图片。
②生成的PDF清晰度太低
当我打印出PDF之后,我又发现了一个问题,就是我的PDF清晰度太低了,像马赛克一样,于是我又去查询问题解决,有两种解决方案,一个增加scale(其实我们在css里面经常看到就是加大比例一样),我尝试了一下,确实可以,但是同时带来的是图片变得很大很大,由于需求是在一个A4纸大小,增进scale:4后字体等等清晰度是高了很多,但是纸张只能够展示出四分之一不到的内容(生成PDF的左上角),这样整个PDF不能全部展示在A4中,显然不如这样做,于是我又查到了一个参数dpi,整个参数的描述非常符合我的预期,但是就是没用,我也不知道是什么问题,可能是版本,因为我查到有一个html2canvas的参数中压根没有这一个参数,总之这条路(最简单)走不通了,于是在带我的大佬的点拨下,有了一个新的思路,就是在html转换成canvas的时候设置scale:2将转换的canvas画板变大,然后在转换成图片后缩小图片的大小,达到增加清晰度(其实和dpi实现思路一样,更麻烦了,因为dpi参数失效了)
html2canvas(page,{
allowTaint: true, //开启跨域
useCORS: true,
scale: 2,
}).then(function(canvas) {
canvas2PDF(canvas);
});
然后在此代码片段,缩小图片的大小,这个方法俗称先放大再缩小,先放大canvas(画板),在将其转换成图片后缩小图片的大小以增加清晰,这个步骤就是可以通俗理解成,你在一个巨大的画板下先画出需要的画面,由于画板很大,即使画的不是很清晰,在绘画完成后,将图片缩小到一个A4纸大小,那么在同样面积内像素就增加了很大,因为从一个巨大的画板都压缩在一个小小A4纸张上了,像素自然就高了(这是我个人理解)
const canvas2PDF = canvas => {
下面位置就是width*0.2
let contentWidth = canvas.width*0.2;
let contentHeight = canvas.height*0.2;
let imgHeight = contentHeight;
let imgWidth = contentWidth;
然后依旧发现清晰度偏低,然后又有了第二个思路,就html代码结构放大,比如原本放在600px的盒子里面,将盒子改成1200px,其实和前面思路一样,一个是增大画板以提升精度,现在这个就是在绘画时候花大一点,那么画板自热而然也会加大,也可以理解成html缩到原来600px的时候像素肯定也会加大,这两个思路在我看来很相似。好的目前生成的图片清晰度已经很高了,基本可以满足公司的需求了,接下来又有一个问题。
③生成的PDF文件过于庞大
由于之前使用的方法中,修改html代码css样式以加大清晰度,那么就会带来一个大小,原来的图片也被放大了,那么如果只是简单再去缩小,图片会非常大,我试过再没被处理的情况下,转换成jpeg的情况下生成pdf大小在13m左右,原图的大小才3m,可见问题之大,这样pdf在文件传输的过程会非常浪费资源,于是我就研究起来了压缩,首先我优化了代码结构,对文件大小改动很小,于是从图片着手,传入图片是20kb左右的时候生成的PDF都依旧有3.8m左右,于是在代码canvas.toDataURL("image/jpeg", 1)处我将参数1修改成0.92,1就是百分百还原图片,0.92就是牺牲清晰度换来文件大小,效果很显著,从3.8m降到了1m,很符合预期,但是我改动了图片清晰度,显然是拆东墙补西墙,我的领导也跟我说这个清晰度较低,希望维持清晰度的情况下尽可能讲到1m,在开始我的认知里面,是越清晰就越大,虽然我也在市面上见过压缩pdf的软件,但是免费情况下都是牺牲清晰度换来文件大小的降低,除了付费情况,所以我开始不知道怎么办,我又去请教带我的那个大佬,大佬直接跟我说,可以啊,随便像压到多小都可以,还不降低清晰度,于是我大佬给了一个.js压缩方法给我,我试了一下直接把3m的压缩到400kb,由于代码是大佬给我的,没经过允许前我就不公开展示了,我写的代码和参考文章代码都放上了,如果有类似需求可以私信我,我可以私发,感谢大佬的分享。方法大概是传入文件,然后将图片转换成base64,然后用canvas处理,然后经过一些我还没研究清楚的方法缩小,如果后续我在网上搜索到公开代码我会在文章中分享出来补充。
最终效果展示:
特别鸣谢:感谢好兄弟某可提出的宝贵意见,已经修改代码处为代码块,经验不足,还请见谅,还有啥不便阅读的欢迎指出