html2canvas 给dom结构转化为canvas,然后生成各种类型图片
jsPDF 把canvas生成的图片url转化为pdf
1. html2canvas
html2canvas(element, options)
- element:要渲染为canvas的HTML元素。这可以是一个DOM元素,也可以是一个选择器字符串,表示需要渲染的元素
- options(可选):一个包含配置选项的对象,用于定制html2canvas的行为
- allowTaint:是否允许加载跨域的图片,默认为false。如果设置为true,html2canvas将会尝试加载跨域的图片,但在某些情况下可能会受浏览器的限制
- backgroundColor:canvas的背景颜色,默认值为#fff
- useCORS:是否使用CORS(Cross-Origin Resource Sharing)来加载图片,默认值为false。如果设置为true,则html2canvas将尝试使用CORS来加载图片
- logging:是否输出日志信息到控制台,默认值为false
- width/height:canvas的宽度和高度。如果未指定,则默认为目标元素的宽度和高度
- scale:缩放因子,决定canvas的分辨率,默认值为window.devicePixelRatio
2. jsPDF
- jsPDF("l", "pt", [width, height])
- 创建一个新的PDF对象,其中"l"表示A4纸张(l:竖向,p:横向),"pt"表示单位为point,width和height分别表示页面宽度和高度
- pdf.setFont(font, size)
- 设置PDF文件的字体和大小。其中font表示字体名称,size表示字体大小
- pdf.setLineWidth(width)
- 设置PDF文件的线宽。其中width表示线宽的像素值
- pdf.drawLine(x1, y1, x2, y2)
- 在PDF文件中绘制一条直线。其中x1、y1和x2、y2分别表示直线的起点和终点坐标
- pdf.drawRect(x, y, width, height)
- 在 PDF 文件中绘制一个矩形。其中x、y分别表示矩形左上角的坐标,width和height分别表示矩形的宽度和高度
- pdf.drawText(text, x, y)
- 在 PDF 文件中绘制一段文本。其中text表示要绘制的文本,x和y分别表示文本的左上角坐标
- pdf.save("filename.pdf")
- 将PDF文件保存为指定的文件名
- pdf.addImage(imageData, type, x, y, width, height)
- 在PDF文件中添加一个图像。其中imageData表示图像的数据,type表示图像的类型,x、y分别表示图像左上角的坐标,width和height分别表示图像的宽度和高度
- pdf.rotate(angle, x, y)
- 旋转PDF文件中的图像或文本
- pdf.translate(x, y)
- 将PDF文件中的文本或图像移动到指定位置
3. 基本实现
- 基本方案
- 写一个基本html页面
- 创建jspdf实例
- 获取页面的dom节点,使用html2canvas将其转化为base64数据流
- 将base64数据流装载到jspdf提供的addImage方法中
- 保存pdf
const htmlToPdf = (element) => {
const option = {
dpi: 192, // dpi属性的值为192,表示图像的分辨率
scale: window.devicePixelRatio, // scale属性的值为设备像素比,表示图像的缩放比例
backgroundColor: "#F1F6FE" // backgroundColor属性的值为"#F1F6FE",表示图像的背景颜色
}
html2canvas(element, option).then((canvas) => {
const contentWidth = canvas.width // 获取canvas对象的宽度
const contentHeight = canvas.height // 获取canvas对象的高度
const pdf = new jsPDF("l", "pt", [contentWidth, contentHeight]) // 创建一个新的PDF对象
const pageData = canvas.toDataURL("image/jpeg", 1.0) // 将Canvas对象转换为JPEG格式的数据,并将其存储在pageData变量中。1.0表示图片质量
pdf.addImage(pageData, "JPEG", 0, 0, contentWidth, contentHeight) // 将JPEG格式的图片添加到PDF文件中,图片的左上角坐标为(0, 0),宽度为contentWidth,高度为contentHeight
pdf.save("test.pdf")
})
}
4. 防截断导出
- 将整个页面全部转成canvas以后,先截取一个A4纸高度页面信息,从下往上判断是否存在一个分界线,如果存在,那就将分界线以下的页面导出成一张pdf。整个操作递归,直到打出到最后一页
- 可以通过getImageData()获取页面像素数据,将像素跟rgba(255,255,255,1)做对比,如果存在10px高度跟宽度为100%页面宽度,且颜色为rgba(255,255,255,1)的间隔条就视为一个分割线
const toChangeRealPdf = (ref, pageName) => {
const margin = 10
html2canvas(ref, {
scale: window.devicePixelRatio * 2 // 增加清晰度
}).then((canvas) => {
// 未生成pdf的canvas页面高度
let leftHeight = canvas.height
// A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
const a4Width = 190
const a4Height = 277
// 一页pdf显示html页面生成的canvas高度 按照宽度缩放
const a4HeightCanvas = Math.floor((canvas.width / a4Width) * a4Height)
const pdf = new jsPDF("p", "mm", "a4")
pdf.setDisplayMode("fullwidth", "continuous", "FullScreen") // 设置显示模式
let position = 0;
let height;
const createImagePdf = (canvas) => {
const canvas1 = document.createElement("canvas")
if (leftHeight > 0) {
let checkCount = 0
if (leftHeight > a4HeightCavnas) {
let i = position + a4HeightCanvas
const canvasW = canvas.width
const mid = canvasW / 2
for (i = position + a4HeightCavnas; i >= position; i--) {
let isWrite = true
for (let j = 0; j < mid; j++) {
// ImageData 对象会有三个属性,height、width 和 data。
// imageData.data:一个一维数组,包含以 RGBA 顺序的数据,数据使用 0 至 255(包含)的整数表示
// 第x行 第y列的像素:
// i = x * width + y
// R --- imageData.data[4 * i + 0] 0 --> 255(黑色到白色)
// G --- imageData.data[4 * i + 1] 0 --> 255(黑色到白色)
// B --- imageData.data[4 * i + 2] 0 --> 255(黑色到白色)
// A --- imageData.data[4 * i + 3] 0 --> 255(透明到不透明)
const c = imageData.data
const index = i * canvasW + j
const lastIndex = i * canvasW - j - 1 // 将页面分成两边,比对的时候,从左右双头进行
const index4 = 4 * index
const lastIndex4 = 4 * lastIndex
if (!(c[index4] == 255 && c[index4 + 1] == 255 && c[index4 + 2] == 255 && c[index4 + 3] == 255) ||
!(c[lastIndex4] == 255 && c[lastIndex4 + 1] == 255 && c[lastIndex4 + 2] == 255 && c[lastIndex + 3] == 255)) {
isWrite = false
break
}
}
if (isWrite) {
checkcount++
if (checkCount >= margin * 2) {
break
}
} else {
checkCount = 0
}
}
height = Math.round(i - postion) || Math.min(leftHeight, a4HeightCavnas)
if (height <= 0) {
height = a4HeightCanvas
}
} else {
height = leftHeight
}
canvas1.width = canvas.width
canvas1.height = height
const ctx = cavnas1.getContext("2d")
ctx.drawImage(canvas, 0, position, canvas.width, height, 0, 0, canvas.width, height) // 剪切图像
if (position != 0) {
pdf.addPage()
}
pdf.addImage(canvas1, "JPEG", a4Width, (a4Width / canvas1.width) * height)
leftHeight -= height
position += height
}
}
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < a4HeightCanvas) {
pdf.addImage(canvas, "JPEG", 10, 10, a4Width, (a4Width / canvas.width) * leftHeight)
} else {
while (leftHeight > 0) {
createImagePdf(canvas)
}
}
pdf.save(pageName + ".pdf")
})
}