需求场景
- 吧页面元素内容转成图片,生成一个海报 保存或者上传服务器
- 当前元素内容生成一个pdf文件 完成下载到客户端,并且上传到服务器
1.开发前的准备
// 元素转图片 npm install --save html2canvas // 图片转pdf文件 npm install jspdf
废话不多说直接上全部核心代码 记得看注释
2.getFile.js
import JsPDF from 'jspdf' import html2Canvas from "html2canvas" // 解决了 pdf分页上一页与下一页内容错开样式不好看问题 // ele:需要截图的元素节点对象 document.querySelector('#pdfDom') // pdfFileName:下载的文件名称 const PdfLoader = (ele, pdfFileName) => { ele.style.fontFamily = '宋体'; ele.style.padding = '0 20px'; // 预留一定的时间给dom页面渲染完成 (如果你能保证dom已经渲染完成了包括图片 才去进行下面转化操作那就可以不用这个延迟器) setTimeout(() => { html2Canvas(ele, { // dpi: 300, // 清晰度 scale: 1, // 将Canvas放大倍数 可以获得更具清晰的图片内容 // !!!注意如果你生成的元素内容非常多是一个非常长列表 建议scale不要写太高或者删除这个属性 ,因为html2Canvas会吧内容转成 // base64 会有一定的内容上限 最终返回没有base64编码(目前我尝试过生成55页的PDF,估计上限在70-100页) useCORS: true, //是否允许跨域 allowTaint: false, height: ele.offsetHeight, width: ele.offsetWidth, windowWidth: document.body.scrollWidth, windowHeight: document.body.scrollHeight, }).then(canvas => { //未生成pdf的html页面高度 var leftHeight = canvas.height; var a4Width = 595.28 var a4Height = 841.89 //一页pdf显示html页面生成的canvas高度; var a4HeightRef = Math.floor(canvas.width / a4Width * a4Height); //pdf页面偏移 var position = 0; var pageData = canvas.toDataURL('image/jpeg', 1.0); // 生成的base64 如果你只是要图片 到这里就可以拿到base64图片编码(可以查一下base64转二进制 使用new FormData对象传给后端到服务器) var pdf = new JsPDF('x', 'pt', 'a4'); //生成A4内容大小的pdf每页 更多参数配置可以看看下面的网站 // https://blog.csdn.net/weixin_42333548/article/details/107630706 var index = 1, canvas1 = document.createElement('canvas'), height; pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen'); // 处理 pdf 上一页 与 下一页内容之间交叉不好看的断点样式 // 并且把内容转成二进制 生成pdf文件 function createImpl(canvas) { if (leftHeight > 0) { index++; var checkCount = 0; if (leftHeight > a4HeightRef) { var i = position + a4HeightRef; for (i = position + a4HeightRef; i >= position; i--) { var isWrite = true for (var j = 0; j < canvas.width; j++) { var c = canvas.getContext('2d').getImageData(j, i, 1, 1).data if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) { isWrite = false break } } if (isWrite) { checkCount++ if (checkCount >= 10) { break } } else { checkCount = 0 } } height = Math.round(i - position) || Math.min(leftHeight, a4HeightRef); if (height <= 0) { height = a4HeightRef; } } else { height = leftHeight; } canvas1.width = canvas.width; canvas1.height = height; var ctx = canvas1.getContext('2d'); ctx.drawImage(canvas, 0, position, canvas.width, height, 0, 0, canvas .width, height); if (position != 0) { pdf.addPage(); } pdf.addImage(canvas1.toDataURL('image/jpeg', 1.0), 'JPEG', 0, 0, a4Width, a4Width / canvas1.width * height); leftHeight -= height; position += height; if (leftHeight > 0) { //给pdf文件 添加全屏水印 // const base64 = ''; // 吧你要添加的水印内容搞成一张小图片 然后手动去转成base64编码 放在这里就可以了 // for (let i = 0; i < 6; i++) { // for (let j = 0; j < 5; j++) { // const left = (j * 120) + 20; // pdf.addImage(base64, 'JPEG', left, i * 150, 20, 30); // }; // }; pdf.addImage(pageData, 'JPEG', 0, i * 150, 20, 30); setTimeout(createImpl, 500, canvas); } else { pdfSave() } } } //当内容未超过pdf一页显示的范围,无需分页 if (leftHeight < a4HeightRef) { pdf.addImage(pageData, 'JPEG', 0, 0, a4Width, a4Width / canvas.width * leftHeight); pdfSave() } else { try { pdf.deletePage(0); setTimeout(createImpl, 500, canvas); } catch (err) { console.log(err); } } function pdfSave() { // pdf文件生成完毕 自动下载到客户本地 pdf.save(pdfFileName + '.pdf') setTimeout(() => { // 吧pdf文件上传到服务器 let base64 = pdf.output("datauristring"); let file = convertBase64ToFile(base64, "报告"); let formData = new FormData(); formData.append("file", file); // 直接post 接口吧formData对象传给后端接口去让后端去获取就可以了 }, 1000) }; }) }, 500) } const convertBase64ToFile = (urlData, filename) => { var arr = urlData.split('base64,'); var type = arr[0].match(/:(.*?);/)[1]; var fileExt = type.split('/')[1]; var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], filename + "." + fileExt, { type: type }); }; export default PdfLoader;
3.页面使用
import PdfLoader from './pdf.js';
PdfLoader(document.getElementById('pdfDom'),'测试文件')
注意:如果你要生成的元素内容有图片 一定要把图片转成base64编码 在页面上去展示不然 在生成pdf或者canvas的时候会跨域,
如果你们的图片地址在腾讯云或者阿里上这种第三方 然后在使用地址转base64遇到了跨域问题 去找后端解决 让他们去配置白名单关闭跨域,(你可以尝试使用百度图库网络地址转base64不会遇到跨域问题)。
// 图片地址转base64 // url:https:xxxxx const getBase64Image = function(url) { function newImg(url) { return new Promise((resolve, reject) => { let image = new Image(); image.crossOrigin = 'Anonymous'; image.src = url; image.onload = function() { resolve(image); }; }); }; return newImg(url).then((img) => { let canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; let ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, img.width, img.height); let ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase(); let dataURL = canvas.toDataURL("image/" + ext); return dataURL; }); }
需求建议:如果你是通过条件查询后端返回数据 来动态生成这个元素 又不想让用户看到你渲染元素的过程,可以像我一样做个一个全局的遮罩弹窗并且展示下载进度 可以有一个更好的交互体验 执行完成后全部统一关闭。
注意:如果你生成的图片就是一个60-100页的pdf内容 想解决分页上限的导致的生成的pdf内容白屏或者黑屏 ,那你可以分段去生成base64编码 在这一步做同步并且吧截图模块做拆分,吧拿到的base64编码进行合并成一个base64编码
最终在去执行写入pdf文件的步骤(下面是base64编码合并),不需要的可以跳过这个代码。
// 友情提示 你要保存每个生成base64的编码以外 还要保存他们的元素高度进行求和不然图片的还原大小会不一致
let img1 = 'data:image/png;base64,iVBORwxxx'// 请替换base64内容 这个太长了我只是占位 let img2 = 'data:image/png;base64,iVBORwxxx' /** * * 合并多张图片,返回新的图片 * @param {Array} list 图片url数组 * @param {Number} cwith 画布宽度 默认500 * @param {Number} cheight 画布高度 默认500 */ function mergeImgs(list, cwith = 500, cheight = 500) { return new Promise((resolve, reject) => { const baseList = [] const canvas = document.createElement('canvas') canvas.width = cwith canvas.height = cheight * list.length const context = canvas.getContext('2d') list.map((item, index) => { const img = new Image() img.src = item // 跨域 img.crossOrigin = 'Anonymous' img.onload = () => { context.drawImage(img, 0, cheight * index, cwith, cheight) const base64 = canvas.toDataURL('image/png') baseList.push(base64) if (baseList[list.length - 1]) { console.log(baseList) // 返回新的图片 resolve(baseList[list.length - 1]) } } }) }) }; const urlList = [img1, img2] mergeImgs(urlList).then(base64 => { const imgDom = document.createElement('img') imgDom.src = base64 document.body.appendChild(imgDom) })
最终给大家看看生成的pdf文件打开的结果。