【PDF】html/dom生成pdf

这篇具有很好参考价值的文章主要介绍了【PDF】html/dom生成pdf。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、简要描述

上一篇博客主要讲的是pdf文件转换成canvas,然后进行相关的画框截图操作。

【PDF】Canvas绘制PDF及截图

本篇博客主要讲html中dom如何生成pdf文件(前端生成pdf),后端生成pdf当然也可以,原理也是将html网页通过后端服务导出成pdf,然后css设置break-after:always;作为分页逻辑,但是我们不深入讲,这里着重讲前端生成pdf。

2、相关插件及知识

还是使用的老朋友jspdf插件和html2canvas

1、jspdf

"jspdf": "^2.5.1"

 使用方法:

import JsPDF from 'jspdf';

const PDF = new jsPDF({
  unit: "mm", // 单位,本示例为mm
  format: "a4", // 页面大小
  orientation: "portrait", // 页面方向,portrait: 纵向,landscape: 横向
  putOnlyUsedFonts: true, // 只包含使用的字体
  compress: true, // 压缩文档
  precision: 16, // 浮点数的精度
});

// 或者

const PDF = new JsPDF('p', 'mm', [210, 297]);

<!-- 常用方法 -->
// 添加图片
PDF.addImage(
  imageData, // 此值可以为下面这些类型 string | HTMLImageElement | HTMLCanvasElement | Uint8Array | RGBAData
  'JPEG', // 转换后的格式
  x, // 被切割的imageData的横坐标
  y, // 被切割的imageData的纵坐标
  w, // 当前图片的宽度
  h, // 当前图片的高度
);
// 添加新的一页
PDF.addPage();
// 输出格式
PDF.output(type: "arraybuffer"): ArrayBuffer;
PDF.output(type: "blob"): Blob;
PDF.output(type: "bloburi" | "bloburl"): URL;
// 本地保存为pdf文件
PDF.save('lindadayo.pdf')

2、html2canvas

"html2canvas": "^1.4.1"
// 实例方法   
html2canvas(dom, config).then(function(canvas) {})

 config相关配置参考下图:【PDF】html/dom生成pdf,pdf

3、源码

1、dom结构

【PDF】html/dom生成pdf,pdf

2、核心逻辑

这里为什么要将dom进行分区处理呢?请看第四点疑难解答中1、为什么要对dom进行分区操作?

    /**
     * 生成pdf
     * @param CommonPage 需要转换的dom节点
     * @param i 分区索引
     * @returns
     */
    async generatePdf(CommonPage?: Element, childLen?: number) {
      PDF = new JsPDF('p', 'mm', [210, 297]); // pdf实例
      for (let i = 0; i < childLen; i++) {
        await asyncSingleAreaControl(CommonPage, i)
      }
      generateUploadPdf();
    },
    /**
     * 上传pdf文件
     */
    async generateUploadPdf() {
      // 文件重命名,修改生成pdf后的文件名
      const pdfName = pdfNameHandle()
      const uri = PDF.output('blob')
      const file = await blobUriToFile(uri, pdfName)
      // 此时的file是File类对象,你可以选择上传到服务器噢~当然你也可以选择直接导出到前端
      // PDF.output('lindadayo.pdf');
    },
    /**
     * 单个分区生成pdf操作
     * @param CommonPage 父节点dom
     * @param i 分区索引
     * @returns
     */
    async asyncSingleAreaControl(CommonPage, i) {
      const canvas = await singleHandle(CommonPage, i)
      await areaPage(canvas, i)
    },
    /**
     * 分区pdf处理
     * @param canvas 各个分区dom转换后的canvas
     * @param areaNo 分区索引
     */
    areaPage(canvas, areaNo) {
      // 是否是第一个分区(作用于是否开始就addPage)
      const isFirstArea = areaNo === 0
      return new Promise((resolve, _reject) => {
        // a4纸宽高
        const A4Origin = {
          width: PDF.internal.pageSize.getWidth(),
          height: PDF.internal.pageSize.getHeight()
        }
        const contentWidth = canvas.width;
        /**
         * html2canvas放大3.125倍时精度丢失导致多了2像素
         * 3368: 高度285mm纸张html2canvas放大300dpi后像素
         * 3366:正常实际高度
         */
        const contentHeight = canvas.height <= 3368 ? 3366 : canvas.height;
        const pageHeight = Math.round(contentWidth / A4Origin.width * A4Origin.height);
        let leftHeight = contentHeight;
        let position = 0;
        const imgWidth = A4Origin.width;
        const imgHeight = Math.ceil(A4Origin.width / contentWidth * contentHeight);
        const pageData = canvas.toDataURL('image/jpeg', 1);
        // 非首个分区,得先addPage,因为不然会少一页 && 大于某个范围才新增一页,避免因为浮点数计算精度造成多增一页
        if (!isFirstArea && leftHeight > 0) {
          PDF.addPage()
        }
        while (leftHeight > 0) {
          PDF.addImage(pageData, 'JPEG', 0, position, imgWidth + (isBrower() ? 0.62 : 0), imgHeight + (isBrower() ? 0.32 : 0));
          position -= A4Origin.height;
          leftHeight -= pageHeight
          // 大于某个范围才新增一页,避免因为浮点数计算精度造成多增一页
          if (leftHeight > 0) {
            PDF.addPage()
          }
        }
        resolve(true)
      })
    },
    /**
     * 单页pdf处理
    //  * @param root 总节点
     * @param index 分区索引
     */
    async singleHandle(CommonPage, index) {
      // 报错Unable to find element in cloned iframe解决方法
      // getDiv在外部声明, 内部赋值
      try {
        getDiv = CommonPage.querySelector(`#CommonPageItemArea-${index}`)
        const res = await html2canvas(getDiv, {
          useCORS: true,
          allowTaint: true,
          scale: 3.125
        }).then(function(canvas) {
          return canvas
        })
        return res
      } catch (e) {
        console.log(e)
      }
    }

4、疑难解答

1、为什么要对dom进行分区操作?

其实如果你不使用html2canvas的参数scale,就没必要进行分区,但是在很多时候,你不放大canvas的话,会导致pdf中的图片很模糊,还有锯齿,所以要对canvas进行方法,但是放大后,会导致一些问题:生成pdf后,超过15000px以后的dom会有样式丢失,所以得对dom进行分区操作,让每个分区的dom高度 * 放大倍数不超过15000px。我们一般都会导出a4纸大小,a4纸宽高是210mm*297mm,换算成像素是793.29px * 1122.52px,如果你选择放大两倍,那么,单页高度就是2245px,结论为一个分区能够放六个a4纸高度的dom,所以你在开发页面时,就要做好这种页面结构噢~

2、html2canvas仍然报图片出错/跨域的问题,即使后端oss已经解决跨域了

报错Error loading image

【PDF】html/dom生成pdf,pdf

这个涉及知识点:img标签实例化获取属于非跨域操作,Image类实例化属于跨域操作,所以得再html2canvas依赖中打补丁,当图片是你本地的静态图片,那不需要转,还是按照Image实例化来做,当图片已经是base64格式的话,也不需要转,赋值给img标签,否则的话加上随机数。

/dist/html2canvas.js 第5759行

【PDF】html/dom生成pdf,pdf

3、报错Unable to find element in cloned iframe解决方法

在分区中循环处理dom生成canvas时会报出这种错误,原因是html2canvas第一参数的变量应该设置为全局变量而不应该是局部变量

    try {
        getDiv = CommonPage.querySelector(`#CommonPageItemArea-${index}`)
        const res = await html2canvas(getDiv, {
          useCORS: true,
          allowTaint: true,
          scale: 3.125
        }).then(function(canvas) {
          return canvas
        })
        return res
      } catch (e) {
        console.log(e)
      }

4、dom-to-image和html2canvas相比,哪个更优?

dom-to-image是一个js库,可以将任意dom节点转换为矢量(SVG)或光栅(PNG或JPEG)图像。和html2canvas相比的话,算是一个新起之秀,更轻巧,相同点就是都会先将dom转成canvas进行操作,所以在dom层级深和多的情况下,还是建议使用html2canvas这种老牌插件

5、生成pdf里图片缺失

那是因为图片转换及获取是异步的,需要时间渲染,所以生成pdf的步骤应该在图片完全加载完之后,由此我们可以加个定时器来循环判断全部图片是否加载完成,加载完成再进行生成操

    /**
     * CommonPage生成dom渲染完成
     * @param callback
     */
    commonPageLoadFinish(callback) {
      nextTick(() => {
        // 生成节点
        const CommonPage = document.querySelector('#CommonPage')
        const childLen = CommonPage.querySelectorAll('.CommonPageItemArea').length
        console.log('分区数量', childLen)
        if (childLen > 0) {
          let timer = null;
          // 监听页面中所有转base64图片是否已生成完毕,如果已生成完毕,则进入下一步与dom相关的操作
          timer = setInterval(() => {
            // isImageAllCompleted.sum => 图片总数量, isImageAllCompleted.loadSum => 目前图片已经加载完的数量
            if (isImageAllCompleted.sum === isImageAllCompleted.loadSum) {
              clearInterval(timer)
              timer = null
              callback(CommonPage, childLen)
            }
          }, 1000)
        }
      })
    }

将生成pdf步骤作为回调函数放在上述函数里

commonPageLoadFinish(generatePdf)

 那在组件中如何监听图片是否加载完成呢?按照以下代码来写

    onMounted(() => {
      nextTick(() => {
        // commonRef.value 为某dom的refs
        // 被动检测是否有图片, 无则直接进入主逻辑
        const img = commonRef.value.querySelectorAll('img');
        if (!img.length) return methods.successCallback();
        let imgSum = 0;
        asyncImgCompLoad(img).then((res) => {
          res.forEach(() => imgSum++);
          // 设置图片总数量和加载数量
          setImageAllCompleted({ sum: isImageAllCompleted.sum + img.length, loadSum: imgSum + isImageAllCompleted.loadSum });
        })
      })
    })


    async asyncImgCompLoad(imgList) {
        const promiseList = []
        for await (const item of imgList) {
          promiseList.push(new Promise((res, rej) => {
            // 参数没有被赋值
            if (!item.src) {
              rej(false)
            }
            if (item.complate) {
              res(true)
            } else {
              item.addEventListener('load', () => {
                res(true)
              })
              // 图片被赋值,但是赋的是错误的值
              item.addEventListener('error', () => {
                rej(false)
              })
            }
          }))
        }
        return Promise.allSettled(promiseList)
      }

 6、生成的pdf里,单页底部有白边?

在使用PDFjs插件时候,加入需要导出a4纸大小,那么很多童鞋就会将宽高固定设置为210mm, 297mm,但是实际上不是整数,是小数,所以获取时按照下述方法获取

        // a4纸宽高
        const A4Origin = {
          width: PDF.internal.pageSize.getWidth(),
          height: PDF.internal.pageSize.getHeight()
        }

PDF分页核心源码

        // a4纸宽高
        const A4Origin = {
          width: PDF.internal.pageSize.getWidth(),
          height: PDF.internal.pageSize.getHeight()
        }
        const contentWidth = canvas.width;
        /**
         * html2canvas放大3.125倍时精度丢失导致多了2像素
         * 3368: 高度285mm纸张html2canvas放大300dpi后像素
         * 3366:正常实际高度
         */
        const contentHeight = canvas.height <= 3368 ? 3366 : canvas.height;
        const pageHeight = Math.round(contentWidth / A4Origin.width * A4Origin.height);
        let leftHeight = contentHeight;
        let position = 0;
        const imgWidth = A4Origin.width;
        const imgHeight = Math.ceil(A4Origin.width / contentWidth * contentHeight);
        const pageData = canvas.toDataURL('image/jpeg', 1);
        // 非首个分区,得先addPage,因为不然会少一页 && 大于某个范围才新增一页,避免因为浮点数计算精度造成多增一页
        if (!isFirstArea && leftHeight > 0) {
          PDF.addPage()
        }
        while (leftHeight > 0) {
          PDF.addImage(pageData, 'JPEG', 0, position, imgWidth + (isBrower() ? 0.62 : 0), imgHeight + (isBrower() ? 0.32 : 0));
          position -= A4Origin.height;
          leftHeight -= pageHeight
          // 大于某个范围才新增一页,避免因为浮点数计算精度造成多增一页
          if (leftHeight > 0) {
            PDF.addPage()
          }
        }

 有两个地方可能童鞋们没看懂,1、首先为啥非首个分区,得先addPage呢?因为PDF默认就有一页,所以你能够直接addImage而不出错,然后后续PDF想要新增一页,都得先addPage,这时候默认背景颜色是白色的,然后再将canvas转成图片,贴到这白板上的,所以你看到PDF文档里有白边,那毫无疑问,就是贴的图片没占完那一页,并且火狐浏览器和谷歌浏览器还有一些细微的差别所以你这就得一点一点微调来达到最佳显示效果。2、为啥addIMage时,里面传的参数不同呢?

PDF.addImage(pageData, 'JPEG', 0, position, imgWidth + (isBrower() ? 0.62 : 0), imgHeight + (isBrower() ? 0.32 : 0));

 这就是浏览器差异问题,火狐浏览器不仅底部有白边,侧面也有白边,相比之下谷歌要更兼容一些。

 7、pdf生成的File文件对象,想要先传入oss,再通过服务端下载怎么实现?

这其实就涉及到大文件上传技术了,因为pdf稍微大点可能都上百M,一般都不会一次性上传完的,所以得做切片上传,然后在服务端合并上传到oss,最后将oss路径地址返回给前端,前端通过这地址去下载。当然具体的大文件上传我就不写在这篇博客了,下一篇博客我将着重讲大文件上传如何写噢~

8、vue-fragment插件配合html2canvas使用有问题?

在@vue/composition-api环境下开发,想使用fragment就得使用vue-fragment插件,但是搭配上html2canvas导出canvas会出现bug,就是只会渲染第一个fragment标签的dom,其他的都不会渲染,目前还是建议在@vue/composition-api环境下不使用fragment

--- 有问题可以随时评论噢~喜欢的请点赞收藏啦 ---文章来源地址https://www.toymoban.com/news/detail-563441.html

到了这里,关于【PDF】html/dom生成pdf的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Flying HTML生成PDF添加水印

    HTML转PDF并添加水印

    2024年01月22日
    浏览(45)
  • Java HTML生成PDF(格式不变)

    Java是大规模和企业级应用程序最常用的编程语言之一。PDF 格式代表可移植文档格式,为人们提供了一种简单、可靠的方式来呈现和交换文档 - 无论查看文档的任何人使用何种软件、硬件或操作系统。因此,pdf 是一种广泛使用的格式,用于在软件应用程序中生成文档。 HTML 用

    2024年02月12日
    浏览(41)
  • 【jspdf】前端html生成pdf的两种办法 以及 引入中文字体

    1、使用canvas把html生成图片,然后使用jspdf生成pdf。优点:生成的pdf 样式还原度极高,缺点:图片形式的pdf无法编辑 2、直接使用jspdf的html方法直接把html生成pdf。优点:可编辑,缺点:只是把文本内容搂出来生成pdf,也就是说样式基本无,而且jspdf不支持中文字体的,如果有中

    2024年02月10日
    浏览(45)
  • 3D数据导出工具HOOPS Publish:3D数据查看、生成标准PDF或HTML文档!

    HOOPS中文网 http://techsoft3d.evget.com/ 一、3D导出SDK HOOPS Publish是一款功能强大的SDK,可以创作丰富的工程数据并将模型文件导出为各种行业标准格式,包括PDF、STEP、JT和3MF。HOOPS Publish核心的3D数据模型是经过ISO认证的PRC格式(ISO 14739-1:2014),它为装配树、拓扑和几何、产品制造信息

    2024年02月10日
    浏览(40)
  • itextpdf7 使用之 html转pdf,生成目录可跳转、添加页眉页脚

    最近有个需求,生成信用报告。 需求: 1、生成pdf有页眉页脚 2、生成目录 3、目录加锚点可跳转。 难点: 1、生成的目录不能实时读取页码 2、目录是后生成的,属于两份pdf拼接的,不能添加锚点跳转 思路: 1、freemaker进行html页面布局及动态变量替换 2、生成一份pdf文档,用

    2024年02月20日
    浏览(35)
  • 【Java】itext 实现 html根据模板生成pdf 中文不显示/图片不显示问题解决

    工作中需要使用生成pdf记录,选取使用的是itext 生成 pdf方式。分享下实现方式及遇到的问题。 这里随便找个html课程表作为示例,添加了几张图片为了展示图片转pdf功能。 一:引入jar包 二:导入ftl文件 这块使用的是html语法,将文件后缀名改为ftl即可,在需要参数的地方通过

    2024年02月05日
    浏览(42)
  • (vue)Vue项目中使用jsPDF和html2canvas生成PDF

    效果: 1.:安装jsPDF和html2canvas 2.在需要生成PDF文档的组件中引入jsPDF和html2canvas 解决参考: 1.https://www.jianshu.com/p/31d37bef539b 2.https://www.php.cn/faq/556634.html 3.https://blog.csdn.net/m0_54967474/article/details/123820384

    2024年02月10日
    浏览(53)
  • vue2如何将页面生成 pdf 导出 html2Canvas + jspdf

    npm i html2canvas npm i jspdf import html2canvas from \\\'html2canvas\\\'; import jsPDF from \\\'jspdf\\\' export const htmlToPDF = async (htmlId, title = \\\"报表\\\", bgColor = \\\"#fff\\\") = {   let pdfDom = document.getElementById(htmlId)   pdfDom.style.padding = \\\'0 10px !important\\\'   const A4Width = 595.28;   const A4Height = 841.89;   let canvas = await html2canvas(pd

    2024年02月16日
    浏览(55)
  • 前端生成pdf之html2canvas+jsPDF,以及解决图片不显示bug

    开发背景: 需要给页面中相应的内容生成pdf,查找文档后发现要用到两个插件。html2canvas 以及 jsPDF html2canvas 给dom结构转化为canvas,然后生成各种类型图片 jsPDF 把canvas 生成的图片url 转化为pdf 参数 image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。

    2024年02月02日
    浏览(52)
  • 【无标题】使用html2canvas和jspdf生成的pdf在不同大小的屏幕下文字大小不一样

    问题:使用html2canvas和jspdf生成的pdf在不同大小的屏幕下文字大小不一样,在mac下,一切正常,看起来很舒服,但是当我把页面放在扩展屏幕下(27寸),再生成一个pdf,虽然排版一样,但是文字就变得非常小 下面的是在mac下的,上面是在扩展屏幕下的,最开始我以为是文字大

    2024年02月16日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包