html2canvas +jsPDF页面导出

253 阅读5分钟

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")
    })
}