html2canvas + jspdf 纯前端HTML导出PDF的实现与问题

前言

        这几天接到一个需求,富文本编辑器的内容不仅要展示出来,还要实现展示的内容导出pdf文件。一开始导出pdf的功能是由后端来做的,然后发现对于宽度太大的图片,导出的pdf文件里部分图片内容被遮盖了,但在前端是正常显示的,只因为class样式后端无法解析。然后,后端开发人员就嫌麻烦,让前端来实现导出pdf的功能。。。

html2canvas(V1.4.1)

         html2canvas 用于将 html 元素渲染成图像,可以将整个页面或特定区域以图像形式进行捕获。这对于将复杂的 html 结构转换为 PDF 格式非常有用,因为它可以捕获 html 中的样式、布局和图像等细节。

        官网:https://html2canvas.hertzen.com/

引用

npm install html2canvas@1.4.1 --save
或
yarn add html2canvas@1.4.1
或
<script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>

示例

<template><div id="pdfBody" style="margin: 30px;"><input class="test_input" type="text" autocomplete="off" placeholder="输入框111"/><input class="test_btn" id="testBtn" type="button" value="按钮"/></div>
</template><script>import html2canvas from "@/utils/htmlToPdf/html2canvas.min";export default {name: "Test",mounted() {document.getElementById('testBtn').addEventListener('click', () => {this.canvasTest();});},methods: {createCanvas(dom) {html2canvas(dom, {useCORS: true, //允许跨域scale: 2, //按比例增加分辨率dpi: 200, //将分辨率提高到特定的DPI(每英寸点数)ignoreElements: (e) => {//过滤head、body等无用的标签元素return !(e.contains(dom) || dom.contains(e) || e.tagName === 'STYLE' || e.tagName === 'LINK');},onclone: (doc) => {//Scoped CSS无法直接应用,需要手动处理let inputDocArr = doc.getElementsByClassName('test_input');[].forEach.call(inputDocArr, inputDoc => {inputDoc.style.height = '36px';inputDoc.style.lineheight = '36px';inputDoc.style.width = '200px';inputDoc.style.margin = '20px';inputDoc.style.borderRadius = '5px';inputDoc.style.border = '1px solid #DCDFE6';});let btnDocArr = doc.getElementsByClassName('test_btn');[].forEach.call(btnDocArr, btnDoc => {btnDoc.style.color = '#FFFFFF';btnDoc.style.backgroundColor = '#1890ff';btnDoc.style.border = '1px solid #1890ff';btnDoc.style.height = '36px';btnDoc.style.width = '80px';btnDoc.style.borderRadius = '20px';});}}).then(canvas => {dom.appendChild(canvas);}).finally(() => {console.log('create canvas finish!');});},canvasTest() {let pdfBody = document.getElementById('pdfBody');this.createCanvas(pdfBody);}}}
</script><style scoped>.test_input {height: 36px;line-height: 36px;width: 200px;margin: 20px;border-radius: 5px;border: 1px solid #DCDFE6;}.test_input:focus {border: 1px solid #1890ff;outline: none;}.test_btn {color: #FFFFFF;background-color: #1890ff;border: 1px solid #1890ff;height: 36px;width: 80px;border-radius: 20px;cursor: pointer;}
</style>

 配置项

        如果想隐藏某个元素不让其显示出来,参考以下几种方案:

        1、元素标签添加 data-html2canvas-ignore 属性,示例如下:

        2、配置项中添加 ignoreElements 属性,手动过滤某个元素,示例如上代码;

        3、配置项中添加 onclone 属性,手动处理某个元素的显示与隐藏,即:element.style.display = 'hidden'。

jsPDF(V1.5.3)

         jsPDF 是一个 PDF 生成库,它允许你通过 JavaScript 代码创建和编辑 PDF 文档。

        官网:https://github.com/parallax/jsPDF

引用

npm install jsPDF@1.5.3 --save
或
yarn add jsPDF@1.5.3
或
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>

 示例

<template><div id="pdfBody" style="margin: 30px;"><h3>测试创建pdf文件!</h3><input id="testPdf" type="button" value="pdf测试"/></div>
</template><script>import jsPDF from "@/utils/htmlToPdf/jspdf.min";export default {name: "Test",mounted() {document.getElementById('testPdf').addEventListener('click', () => {this.pdfTest();});},methods: {pdfTest() {let doc = new jsPDF({orientation: 'p',unit: 'mm',format: 'a4'});doc.text("Hello world!", 10, 10);doc.save("测试.pdf");}}}
</script>

设置中文字体

        设置英文内容是正常显示的,假若内容包含中文,会发现pdf文件里的内容显示乱码,如下图所示:

let doc = new jsPDF({orientation: 'p',    unit: 'mm',format: 'a4'
});
doc.text("pdf文件测试!", 10, 10);
doc.save("测试.pdf");

        解决这一问题,可以使用 jsPDF 提供的 setFont(font) 方法,具体操作如下:

        1、网上下载一个支持中文的字体tff文件,或者拷贝一份本地 window/font/ 路径下的字体文件(ps:有的字体不支持);

        2、从GitHub上下载jsPDF源码,然后打开fontconverter目录下的fontconverter.html文件;

        3、选择本地的tff文件,点击“Create”按钮,会生成一个js文件;

        4、一般情况下,生成的js文件直接引入页面使用会报错,需要手动处理生成新的js文件;

        默认生成的js文件内容如下:

(function (jsPDFAPI) {var font = "XXXXXX";var callAddFont = function () {this.addFileToVFS("simfang.ttf", font);this.addFont("simfang.ttf", "simfang", "normal");
};
jsPDFAPI.events.push(['addFonts', callAddFont])})(jsPDF.API);

        然后复制一份js文件,编辑内容如下格式:

export function addfont(pdf) {let font = 'XXXXXX';pdf.addFileToVFS('simfang.ttf', font);pdf.addFont('simfang.ttf', 'simfang', 'normal');
}

        5、将处理好的新的js文件放入项目中,然后在页面中引用,并设置字体。

<template><div id="pdfBody" style="margin: 30px;"><h3>测试创建pdf文件!</h3><input id="testPdf" type="button" value="pdf测试"/></div>
</template><script>import jsPDF from "@/utils/htmlToPdf/jspdf.min";import {addfont} from '@/utils/htmlToPdf/simfang';export default {name: "Test",mounted() {document.getElementById('testPdf').addEventListener('click', () => {this.pdfTest();});},methods: {pdfTest() {let doc = new jsPDF({orientation: 'p',unit: 'mm',format: 'a4'});//设置中文字体addfont(doc);doc.setFont('simfang');doc.text("pdf文件测试撒大大大飒飒热通过如果还有就要让他阿松大尔特瑞特好几年,是否然后他又就很尴尬发射任务给他人用不上,二条天皇已经不是v堵塞惹我实在是肉体和set一年德国必须色让他管很多人把!", 10, 10);doc.save("测试.pdf");}}}
</script>

解决jsPDF文本不自动换行问题

        中文乱码问题解决了,然后新的问题又出现了,文本不自动换行。解决方案参考如下:

        1、使用 splitTextToSize() 方法:

let doc = new jsPDF({orientation: 'p',unit: 'mm',format: 'a4'    
});
//设置中文字体
addfont(doc);
doc.setFont('simfang');
let con = "pdf文件测试撒大大大飒飒热通过如果还有就要让他阿松大尔特瑞特好几年,是否然后他又就很尴尬发射任务给他人用不上,二条天皇已经不是v堵塞惹我实在是肉体和set一年德国必须色让他管很多人把!";
let splitCon = doc.splitTextToSize(con, 190);
console.log('splitCon',splitCon)
doc.text(splitCon, 10, 10);
doc.save("测试.pdf");

        2、text() 方法参数添加选项 maxWidth :

let doc = new jsPDF({orientation: 'p',unit: 'mm',format: 'a4'
});
//设置中文字体
addfont(doc);
doc.setFont('simfang');
let con = "pdf文件测试撒大大大飒飒热通过如果还有就要让他阿松大尔特瑞特好几年,是否然后他又就很尴尬发射任务给他人用不上,二条天皇已经不是v堵塞惹我实在是肉体和set一年德国必须色让他管很多人把!";
doc.text(con, 10, 10, {maxWidth: 190
});
doc.save("测试.pdf");

jsPDF 库 API

        文档地址:https://artskydj.github.io/jsPDF/docs/index.html

        以下是常用API,仅供参考:

方法说明
var doc = new jsPDF(orientation, unit, format, compress);

创建新文档:

orientation:"l"(横向)、"p"(纵向);

unit:"pt"、"mm"(默认)、"cm"、"m"、"in" or "px";

format:"a3"、"a4(默认)"、"a5"、"letter"、"legal"等

doc.addPage()添加一个空白页
doc.setPage(pageNumber)切换到第几个页面操作
doc.internal.getNumberOfPages()获取总页面数
doc.text(text, x, y, options);

页面中添加文本:

text:文本内容;

x:距离页面左边的距离;

y:距离页面上边的距离;

options:可选参数配置

doc.setFont(fontName, fontStyle)

页面文本设置字体:

fontName:字体名称;

fontStyle:字体风格,如"加粗"

doc.setFontSize(size);设置字体大小
doc.setTextColor(ch1, ch2, ch3, ch4);设置文本颜色,可以是颜色码或rgb值
doc.addImage(imageData,format, x, y, width, height)

在 PDF 文件中添加一个图像:

imageData:图像的数据;

format:图像的类型,如"JPEG";

x、y:分别表示图像左上角的坐标;

width:图像的宽度;

height:图像的高度

doc.save(filename)生成指定文件名的PDF文件

html2canvas + jsPDF

        jsPDF 与 html2canvas 结合使用,可以将 html 元素渲染成图像,然后将图像插入到 jsPDF 创建的 PDF 文档中。

示例

import html2canvas from "@/utils/htmlToPdf/html2canvas.min";
import jsPDF from "@/utils/htmlToPdf/jspdf.min";
import {addfont} from '@/utils/htmlToPdf/simfang';
import {Loading} from "element-ui";export function downloadPdf(title, dom) {let reqLoading = Loading.service({fullscreen: true,text: '正在生成PDF文件......',spinner: 'el-icon-loading',background: 'rgba(0,0,0,0.5)'});html2canvas(dom, {useCORS: true, //允许跨域scale: 2, //按比例增加分辨率dpi: 200, //将分辨率提高到特定的DPI(每英寸点数)ignoreElements: (e) => {return !(e.contains(dom) || dom.contains(e) || e.tagName === 'STYLE' || e.tagName === 'LINK');}}).then(canvas => {// dom.appendChild(canvas);// 新建JsPDF对象let PDF = new jsPDF({orientation: 'p', //参数: l:横向  p:纵向unit: 'mm', //参数:测量单位("pt","mm", "cm", "m", "in" or "px")format: 'a4', //A4纸});let ctx = canvas.getContext('2d');let a4w = 190;let a4h = 272; //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277,底部留5mm的页码显示位置,所以高度为272mm。let imgHeight = Math.floor(a4h * canvas.width / a4w) - 2; //按A4显示比例换算一页图像的像素高度let renderedHeight = 0;//计算总页数let pageCount = Math.ceil(canvas.height / imgHeight);let page = document.createElement("canvas");page.width = canvas.width;//设置中文字体addfont(PDF);PDF.setFont('simfang');while (renderedHeight < canvas.height) {page.height = Math.min(imgHeight, canvas.height - renderedHeight); //可能内容不足一页//用getImageData剪裁指定区域,并画到前面创建的canvas对象中page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0);//canvas转图片数据保留10mm边距PDF.addImage(page.toDataURL('image/jpeg', 1), 'JPEG', 10, 10, a4w, Math.min(a4h, a4w * page.height / page.width));renderedHeight += imgHeight;//计算当前页及文字let pageNumberText = '第' + (renderedHeight / imgHeight) + '页/共' + pageCount + '页';//设置文字大小PDF.setFontSize(4);//设置文字颜色PDF.setTextColor('#999');//在页脚添加页码PDF.text(pageNumberText, ((a4w + 8) / 2), (a4h + 16));//判断是否分页,如果后面还有内容,添加一个空页if (renderedHeight < canvas.height) {PDF.addPage();}}PDF.save(title + ".pdf");}).finally(() => {if (reqLoading) {reqLoading.close();}});
}

常见问题

截取的图片显示空白

1、跨域问题

        html2canvas 默认是不支持跨域图片的,对于跨域的图片默认是无法截取进去的,需要开启跨域配置:

  1. 将图片转成 base64 形式;
  2. 设置配置项 allowTaint: true 或 useCORS: true;
  3. img 标签中添加 crossOrigin="anonymous";
  4. 图片服务器配置 Access-Control-Allow-Origin: *

2、图片未加载完成

        若图片未加载完成,此时截取的也是空白,解决方法就是设置延时一定事件后处理,或图片添加 load 事件,等图片加载完在进行截图。

3、需要截图的dom元素太多

        若出现前面内容都正常截取,后面内容出现空白,大概率就是dom元素太多了。本人就碰到这种情况,即使延时10秒后截取,后面的部分还是空白,没办法,最后只能后端实现导出pdf功能了。。。

截图后部分css效果未正常显示

1、部分css样式 html2canvas 不支持

        html2canvas 无法支持全部的css样式,关于支持哪些css样式或不支持哪些css样式,可以参考文档:https://html2canvas.hertzen.com/features

2、scoped 作用域的css无法应用

        在vue中,为了避免css样式的交叉污染,都会使用 scoped 作用域的css样式,但是该作用域下的css样式无法在 html2canvas 中使用,解决方案可以在 onclone 配置项中过滤元素手动处理样式(详情可参考上述示例)

        其他的问题可以参考博文:https://www.cnblogs.com/padding1015/p/9225517.html

总结

        使用 html2canvas + jsPDF 纯前端导出pdf的方式还是有很多问题的,一般情况下都是后端进行文件的导出,前端配合解决样式问题。

        另外我看还有使用 dom-to-image 或 modern-screenshot 的,不知道效果怎么样,有时间可以试一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/46499.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Node.js】会话控制

express 中操作 cookie cookie 是保存在浏览器端的一小块数据。 cookie 是按照域名划分保存的。 浏览器向服务器发送请求时&#xff0c;会自动将 当前域名下可用的 cookie 设置在请求头中&#xff0c;然后传递给服务器。 这个请求头的名字也叫 cookie &#xff0c;所以将 c…

LLaMA 背景

什么是LLaMA&#xff1f; 模型介绍&#xff1a;LLaMA是Meta开发的语言模型&#xff0c;旨在推动开放科学和人工智能的实践。 参数范围&#xff1a;这些模型的参数数量从7B到65B不等&#xff0c;覆盖了不同规模的需求。 训练数据&#xff1a;LLaMA模型是在数万亿个公开数据集的…

Python算法实现之排序算法的Python实现详解

概要 排序算法是计算机科学中最基础和最重要的算法之一。它们在数据处理中起着关键作用,广泛应用于搜索、数据分析和优化等领域。本文将详细介绍几种常见的排序算法及其Python实现,包括冒泡排序、选择排序、插入排序、归并排序和快速排序,并通过具体示例代码展示它们的工作…

推荐一款基于Spring Boot 框架开发的分布式文件管理系统,功能齐全,非常便捷(带私活源码)

前言 在数字化时代&#xff0c;文件管理是企业和个人用户的基本需求。然而&#xff0c;现有的文件管理系统往往存在一些痛点&#xff0c;如存储空间有限、文件共享困难、缺乏在线编辑功能、移动端适配性差等。这些问题限制了用户在不同设备和场景下的文件处理能力。 为了解决…

【20】读感 - 架构整洁之道(二)

概述 继上一篇文章讲了前两章的读感&#xff0c;已经归纳总结的重点&#xff0c;这章会继续跟进的看一下&#xff0c;深挖架构整洁之道。 编程范式 编程范式从早期到至今&#xff0c;提过哪些编程范式&#xff0c;结构化编程&#xff0c;面向对象编程&#xff0c;函数式编程…

ClickHouse 入门(二)【基础SQL操作】

1、ClickHouse 1.1、SQL 操作 这里只介绍一些和我们之前 MySQL 不同的语法&#xff1b; 1.1.1、Update 和 Delete ClickHouse 提供了 Delete 和 Update 的能力&#xff0c;这类操作被称为 Mutation 查询&#xff08;可变查询&#xff09;&#xff0c;它可以看 做 Alter 的一…

负载均衡 lvs

1. 4层转发(L4) 与 7层转发(L7) 区别 4层转发(L4) 与 7层转发(L7) 区别 转发基于的信息 状态 常用的服务 L4 基于网络层和传输层信息&#xff1a; L4转发主要依赖于网络层IP头部(源地址&#xff0c;目标地址&#xff0c;源端口&#xff0c;目标端口)和传输层头部&#xff…

珈和科技完成全国首个农险服务类数据产品入表,实现数据资产化

近日&#xff0c;珈和科技与东湖大数据合作&#xff0c;完成全国首个保险服务类数据产品入表&#xff0c;标志着我国商业卫星遥感应用领域迈出了数据资产化的关键一步。 此次入表的数据产品为“华北农业保险服务数据集数据产品”&#xff0c;是珈和科技融合卫星遥感与无人机等…

新华三H3CNE网络工程师认证—VLAN使用场景与原理

通过华三的技术原理与VLAN配置来学习&#xff0c;首先介绍VLAN&#xff0c;然后介绍VLAN的基本原理&#xff0c;最后介绍VLAN的基本配置。 一、传统以太网问题 在传统网络中&#xff0c;交换机的数量足够多就会出现问题&#xff0c;广播域变得很大&#xff0c;分割广播域需要…

前端学习(二)之HTML

一、HTML文件结构 <!DOCTYPE html> <!-- 告诉浏览器&#xff0c;这是一个HTML文件 --><html lang"en"> <!-- 根元素&#xff08;起始点&#xff0c;最外层容器&#xff09; --><head> <!-- 文档的头部&#xff08;元信息&#xff…

Typora 1.5.8 版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora是一款基于Markdown语法的轻量级文本编辑器&#xff0c;它的主要目标是为用户提供一个简洁、高效的写作环境。以下是Typora的一些主要特点和功能&#xff1a; 实时预览&#xff1a;Typora支持实时预览功能&#xff0…

实战篇(十一) : 拥抱交互的三维世界:利用 Processing 和 OpenGL 实现炫彩粒子系统

🌌 拥抱交互的三维世界:利用 Processing 和 OpenGL 实现炫彩粒子系统 在现代计算机图形学中,三维粒子系统是一个激动人心的领域。它不仅可以用来模拟自然现象,如烟雾、火焰和水流,还可以用来创造出令人叹为观止的视觉效果。在这篇文章中,我们将深入探讨如何使用 Proces…

【linux】服务器安装NVIDIA驱动

【linux】服务器安装NVIDIA驱动 【创作不易&#xff0c;求点赞关注收藏】&#x1f600; 文章目录 【linux】服务器安装NVIDIA驱动一、关闭系统自带驱动nouveau二、下载英伟达驱动三、安装英伟达驱动1、禁用X服务器和相关进程2、在TTY终端安装驱动3、验证是否安装成功4、重新启…

最新开源的解析效果非常好的PDF解析工具MinerU (pdf2md pdf2json)

毫不夸张的说 PDF解析工具MinerU是照进RAG黑暗中的一道光——这是我对它的评价。我测过太多了文档解析工具&#xff01; 最近在做文档解析的工作。看了很多的开源的文档解析的工具&#xff0c;版面分析的工具&#xff0c;其中包括paddelpaddel这样30kstar的明星工具。但是效果都…

01 安装

安装和卸载中&#xff0c;用户全部切换为root&#xff0c;一旦安装&#xff0c;普通用户也能使用 初期不进行用户管理&#xff0c;全部用root进行&#xff0c;使用mysql语句 1. 卸载内置环境 检查是否有mariadb存在&#xff0c;存在走a部分卸载 ps axj | grep mysql ps ajx |…

逻辑门的题目怎么做?

FPGA语法练习——二输入逻辑门&#xff0c;一起来听~~ FPGA语法练习——二输入逻辑门 题目介绍&#xff1a;F学社-全球FPGA技术提升平台 (zzfpga.com)

低代码中间件学习体验分享:业务系统的创新引擎

前言 星云低代码平台介绍 星云低代码中间件主要面向企业IT部门、软件实施部门的低代码开发平台&#xff0c;无需学习开发语言/技术框架&#xff0c;可视化开发PC网页/PC项目/小程序/安卓/IOS原生移动应用&#xff0c;低门槛&#xff0c;高效率。针对企业研发部门人员少&#…

什么是正则表达式,如何在 Python 中使用?

什么是正则表达式 正则表达式&#xff08;Regular Expression&#xff0c;简称Regex&#xff09;是一种用于匹配字符串中字符模式的工具。它是由普通字符&#xff08;例如字母、数字&#xff09;以及一些特殊字符&#xff08;称为元字符&#xff09;组成的字符序列。这种模式用…

Spring MVC-什么是Spring MVC?

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 1.MVC定义2. Spring MVC 官方对于Spring Web MVC的描述这样的: Spring Web MVC is the original web framework built on the Servlet APl and has been includedin the Spring Frame…

node解析Excel中的考试题并实现在线做题功能

1、背景 最近公司安排业务技能考试&#xff0c;下发excel文件的题库&#xff0c;在excel里查看并不是很方便&#xff0c;就想着像学习驾考题目一样&#xff0c;一边看一边做&#xff0c;做完之后可以查看正确答案。 2、开始分析需求 题目格式如下图 需求比较简单&#xff0c;…