html2canvas+jspdf实现下载pdf文件并添加水印

 更新时间:2024年10月29日 09:47:36   作者:嘻嘻哈哈猿人  
这篇文章主要为大家详细介绍了如何使用html2canvas + jspdf进行下载pdf文件添加水印,以及echarts图片防止截断处理,有需要的小伙伴可以了解下

背景

一开始实现长截图使用 html2canvas 直接搞定, 后来改成需要下载 pdf 文件, ... 查找相关内容通过改造最终全部完成实现

  • 前端实现pdf下
  • echarts不能截断
  • 下载后的内容添加水印

jsPdf文档

html部分

// 使用class 或者 ref 获取都可以
<div class="pdfDom" ref="pdfDom"> 
   <div data-content-group="content-group" > 不能被截断的内容 </div>
    <div data-content-item="content-item"> 不能被截断的内容 </div>
    <div data-content-item="content-item"> 不能被截断的内容 </div>
    <div data-content-item="content-item"> 不能被截断的内容 </div>
    <div data-content-group="content-group">
        <div data-content-item="content-item"> 嵌套的内容不能被截断 </div>
        <div data-content-item="content-item"> 嵌套的内容不能被截断 </div>
        <div data-content-item="content-item"> 嵌套的内容不能被截断 </div>
    </div>
</div>

js部分

import JsPdf from "jspdf";
import html2Canvas from "html2canvas";
import "./simhei-normal.js" // 解决pdf添加水印中文字体乱码问题 ttf转为js后引用

// 这里使用pdf添加水印
function addWatermark (pdf, watermarkText) {
  // 获取PDF页数,给PDF每一页添加水印
   var totalPages = pdf.internal.getNumberOfPages();
   for (let i = 1; i <= totalPages; i++) {
     pdf.setPage(i);
     addWatermarkFill(pdf, watermarkText);
   }
   return pdf;
}
function addWatermarkFill (pdf, watermarkText) {
 let pdfW = pdf.internal.pageSize.getWidth();
 let pdfH = pdf.internal.pageSize.getHeight();
 // 250是可以根据水印的大小调整的 可以优化为单水印的长和宽
 let xCount = pdfW / 250; 
 let yCount = pdfH / 250;
 // 下面的for循环的作用是在页面上铺满水印
 for (let y = 0; y < yCount; y++) {
   let yLocation = y * 250;
   for (let x = 0; x < xCount; x++) {
     let xLocation = x * 250;
     pdf.saveGraphicsState() // 保存图形状态
     pdf.setFontSize(18);       // 设置水印字体大小
     pdf.setTextColor(200);    // 设置水印颜色
    //  https://artskydj.github.io/jsPDF/docs/jsPDF.html#setTextColor
     pdf.setGState(pdf.GState({ opacity: 0.3 })) // 设置透明度为0.3
     // 这里的SIMHEI是字体包 解决jsPDF 无法使用中文问题
    //  pdf.setFont('Alternate-normal');
     pdf.setFont('SIMHEI');
    //  https://artskydj.github.io/jsPDF/docs/jsPDF.html#setFont
     pdf.text(watermarkText, xLocation, yLocation, { align: 'left', angle: -45});
     pdf.restoreGraphicsState()
   }
 }
 return pdf;
}
// 以上代码为添加水印逻辑处理
// pdfDom 页面dom , intervalHeight 留白间距  fileName 文件名 watermarkText: 水印内容
export function html2Pdf (pdfDom, intervalHeight, fileName, watermarkText) {
  // 获取元素的高度
  function getElementHeight (element) {
    return element.offsetHeight;
  }
  // A4 纸宽高
  const A4_WIDTH = 592.28; const A4_HEIGHT = 841.89;
  // 获取元素去除滚动条的高度
  const domScrollHeight = pdfDom.scrollHeight;
  const domScrollWidth = pdfDom.scrollWidth;
  // 保存当前页的已使用高度
  let currentPageHeight = 0;
  // 获取所有的元素  我这儿是手动给页面添加class 用于计算高度 你也可以动态添加 这个不重要,主要是看逻辑
  // let elements = pdfDom.querySelectorAll('.content-item'); // 没有嵌套逻辑可以直接获取
  // 代表不可被分页的
  const newPage = "content-item"; 
  const wholeNodes = []; // 嵌套元素遍历出来 push
  // traversingNodes 当不可截断元素过大时候 递归获取子元素 
  function traversingNodes (nodes) {
    if (nodes.length === 0) return;
    nodes.forEach(element => {
      if (element.nodeType !== 1) return;
      // contentItem 不能截断标识
      // contentGroup 嵌套的父级标识
      const { contentItem: item, contentGroup: group } = element.dataset;
      if (item) {
        const elementHeight = getElementHeight(element);
        console.log(elementHeight, "我是页面上的elementHeight"); // 检查
        // 检查添加这个元素后的总高度是否超过 A4 纸的高度
        if (currentPageHeight + elementHeight > A4_HEIGHT) {
        // 如果超过了,创建一个新的页面,并将这个元素添加到新的页面上
          currentPageHeight = elementHeight;
          element.classList.add(newPage);
          console.log(element, "我是相加高度大于A4纸的元素");
        }
        currentPageHeight += elementHeight;
        wholeNodes.push(element); // 不可截断元素存在嵌套
      } else if (group) {
        traversingNodes(element.childNodes);
      }
    });
  }
  traversingNodes(pdfDom.childNodes);
  // 遍历所有内容的高度
  // 根据 A4 的宽高等比计算 dom 页面对应的高度

  const pageWidth = pdfDom.offsetWidth;
  const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
  // 将所有不允许被截断的子元素进行处理 如果没有嵌套逻辑可以直接获取 有嵌套逻辑 通过 traversingNodes 遍历push
  // const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`);
  console.log(wholeNodes, "将所有不允许被截断的子元素进行处理");
  // 插入空白块的总高度
  let allEmptyNodeHeight = 0;
  for (let i = 0; i < wholeNodes.length; i++) {
    // 判断当前的不可分页元素是否在两页显示
    const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight);
    const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight);

    // 是否被截断
    if (topPageNum !== bottomPageNum) {
      // 创建间距
      const newBlock = document.createElement("div");
      newBlock.className = "empty-node";
      newBlock.style.background = "#FFFFFF";

      // 计算空白块的高度,可以适当留出空间,根据自己需求而定
      const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
      newBlock.style.height = _H + intervalHeight + "px";

      // 插入空白块
      wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]);

      // 更新插入空白块的总高度
      allEmptyNodeHeight = allEmptyNodeHeight + _H + intervalHeight;
    }
  }
  // 这里可以不加 页面高度会变
  //  pdfDom.setAttribute(
  //   'style',
  //   `height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
  //   )

  return html2Canvas(pdfDom, {
    width: pdfDom.offsetWidth,
    height: pdfDom.offsetHeight,
    useCORS: true,
    allowTaint: true
    // scale: 1
  }).then(canvas => {
    // dom 已经转换为 canvas 对象,可以将插入的空白块删除了
    const emptyNodes = pdfDom.querySelectorAll(".empty-node");
    for (let i = 0; i < emptyNodes.length; i++) {
      emptyNodes[i].style.height = 0;
      emptyNodes[i].parentNode.removeChild(emptyNodes[i]);
    }
    
    const canvasWidth = canvas.width; const canvasHeight = canvas.height;
    // html 页面实际高度
    let htmlHeight = canvasHeight;
    // 页面偏移量
    let position = 0;
   
    // 根据 A4 的宽高等比计算 pdf 页面对应的高度
    const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT;
   
    // html 页面生成的 canvas 在 pdf 中图片的宽高
    const imgWidth = A4_WIDTH;
    const imgHeight = 592.28 / canvasWidth * canvasHeight;
    // 将图片转为 base64 格式
    const imageData = canvas.toDataURL("image/jpeg", 1.0);
    
    
    // 生成 pdf 实例
    let PDF = new JsPdf("", "pt", "a4", true); 
    // html 页面的实际高度小于生成 pdf 的页面高度时,即内容未超过 pdf 一页显示的范围,无需分页
    if (htmlHeight <= pageHeight) {
      PDF.addImage(imageData, "JPEG", 0, 0, imgWidth, imgHeight);
    } else {
      while (htmlHeight > 0) {
        PDF.addImage(imageData, "JPEG", 0, position, imgWidth, imgHeight);
        // 更新高度与偏移量
        htmlHeight -= pageHeight;
        position -= A4_HEIGHT;
        if (htmlHeight > 0) {
          // 在 PDF 文档中添加新页面
          PDF.addPage();
        }
      }
    }
    PDF = addWatermark(PDF, watermarkText );
    // 保存 pdf 文件
    PDF.save(`${fileName}.pdf`);
  }).catch(err => {
    console.log(err);
  });
}

调用

    import { html2Pdf } from "./outpdf.js";

    const pdfDom = ref();
    html2Pdf(pdfDom.value, 20, "下载PDF文件", "水印文字");

水印添加问题

添加水印的时候遇到了中文乱码问题 查看文档jspdf 是支持自定义字体的

在电脑中找到找到font文件 也可以在网上下载对应的 ttf 文件 我这里用的 黑体常规

ttf文件转换为 js 文件 ttf文件转js地址jsPdf文档首页中有讲到Use of UTF-8 / TTF:

将转换后的js文件,导入到工程内用,在文件内引入,用jsPDF里的 setFont()方法加载一下字体包

到此这篇关于html2canvas+jspdf实现下载pdf文件并添加水印的文章就介绍到这了,更多相关html2canvas jspdf下载pdf内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JS加载器如何动态加载外部js文件

    JS加载器如何动态加载外部js文件

    这篇文章主要介绍了JS加载器如何动态加载外部js文件的相关资料,需要的朋友可以参考下
    2016-05-05
  • Js实现累加上漂浮动画示例

    Js实现累加上漂浮动画示例

    这篇文章主要为大家介绍了Js实现累加上漂浮动画示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Javascript 定时器调用传递参数的方法

    Javascript 定时器调用传递参数的方法

    Javascript 定时器调用传递参数的方法,需要的朋友可以参考下。
    2009-11-11
  • Javascript表单验证要注意的事项

    Javascript表单验证要注意的事项

    JavaScript 可用来在数据被送往服务器前对 HTML 表单中的这些输入数据进行验证。被 JavaScript 验证的这些典型的表单数据有:用户是否已填写表单中的必填项目?用户输入的邮件地址是否合法?用户是否已输入合法的日期?用户是否在数据域 (numeric field) 中输入了文本?
    2014-09-09
  • 微信小程序中如何使用store数据共享

    微信小程序中如何使用store数据共享

    全局数据共享 全局数据共享(状态管理)是为了解决组件之间数据共享的问题,开发中常用的全局数据共享方案有:Vuex、Redux、MobX等,这篇文章主要介绍了微信小程序中如何使用store数据共享,需要的朋友可以参考下
    2023-04-04
  • JavaScript模拟实现Promise功能的示例代码

    JavaScript模拟实现Promise功能的示例代码

    这篇文章主要为大家详细介绍了JavaScript如何模拟实现Promise功能,文中的示例代码讲解详细,对我们学习JavaScript有一定的帮助,需要的可以参考一下
    2022-12-12
  • iview通过Dropdown(下拉菜单)实现的右键菜单

    iview通过Dropdown(下拉菜单)实现的右键菜单

    这篇文章主要介绍了iview通过Dropdown(下拉菜单)实现的右键菜单 ,需要的朋友可以参考下
    2018-10-10
  • setinterval()与clearInterval()JS函数的调用方法

    setinterval()与clearInterval()JS函数的调用方法

    这篇文章主要介绍了setinterval()与clearInterval()JS函数的调用方法,实例分析了setinterval()与clearInterval()的语法结构及使用技巧,需要的朋友可以参考下
    2015-01-01
  • 老生常谈JavaScript 正则表达式语法

    老生常谈JavaScript 正则表达式语法

    下面小编就为大家带来一篇老生常谈JavaScript 正则表达式语法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • JS立即执行的匿名函数用法分析

    JS立即执行的匿名函数用法分析

    这篇文章主要介绍了JS立即执行的匿名函数,结合实例形式分析了;(function() {})();相关原理、使用技巧及操作注意事项,需要的朋友可以参考下
    2019-11-11

最新评论