html2canvas 与 jspdf 相结合生成 pdf 内容被截断的终极解决方案

4,552 阅读6分钟

欢迎关注我的公众号

《人生代码》

我有一个大胆的想法,我要一直写到死,那一天我不写了,可能就死了。哈哈。

哈喽,大家好,我是你们的攻城狮,人贱人爱的 Ken,一个永远充满激情的人。

最近接收到一个优化需求:就是对之前的行程文档的图文介绍添加打印生成 pdf 的功能

当然,我们需要依赖 html2canvas 和 jspdf.min.js 这两个库,html2canvas 是用于生成 canvas,jspdf.min.js 是用于生成 pdf 的。

首先我们需要引入 html2canvas, jspdf

import html2canvas from 'html2canvas'
import './jspdf.min.js'

如果有滚动条,先把滚动条置顶

document.body.scrollTop = document.documentElement.scrollTop = 0
if (!selector) {
    throw new Error('缺少selector')
}
let el = document.querySelector(selector)
if (!el) {
    throw new Error('未找到' + selector + '对应的dom节点')
}

设置背景色为白色,然后转成图片后,获取截断处图片像素点,从截断处往上一行行扫描像素点颜色,碰到这一行颜色都是全白的,代表是从这里开始截断,将这个高度开始将往下的内容都放到下一页

 html2canvas(el, {
    allowTaint: true,
    useCORS: true,
    dpi: 120, // 图片清晰度问题
    background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色,这里是个坑
  }).then(canvas => {
    //未生成pdf的html页面高度
    var leftHeight = canvas.height

    var a4Width = 595.28
    var a4Height = 841.89 //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
    //一页pdf显示html页面生成的canvas高度;
    var a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height)

    //pdf页面偏移
    var position = 0

    var pageData = canvas.toDataURL('image/jpeg'1.0)

    var pdf = new jsPDF('p''pt''a4') //A4纸,纵向
    var index = 1,
      canvas1 = document.createElement('canvas'),
      height
    pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen')

    var pdfName = title
    function createImpl(canvas) {
      console.log(leftHeight, a4HeightRef)
      if (leftHeight > 0) {
        index++

        var checkCount = 0
        if (leftHeight > a4HeightRef) {
          var i = position + a4HeightRef
          for (i = position + a4HeightRef; i >= position; i--) {
            var isWrite = true
            for (var j = 0; j < canvas.width; j++) {
              var c = canvas.getContext('2d').getImageData(j, i, 11).data

              if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {
                isWrite = false
                break
              }
            }
            if (isWrite) {
              checkCount++
              if (checkCount >= 10) {
                break
              }
            } else {
              checkCount = 0
            }
          }
          height = Math.round(i - position) || Math.min(leftHeight, a4HeightRef)
          if (height <= 0) {
            height = a4HeightRef
          }
        } else {
          height = leftHeight
        }

        canvas1.width = canvas.width
        canvas1.height = height

        console.log(index, 'height:', height, 'pos', position)

        var ctx = canvas1.getContext('2d')
        ctx.drawImage(
          canvas,
          0,
          position,
          canvas.width,
          height,
          0,
          0,
          canvas.width,
          height,
        )

        var pageHeight = Math.round((a4Width / canvas.width) * height)
        // pdf.setPageSize(null, pageHeight)
        if (position != 0) {
          pdf.addPage()
        }
        pdf.addImage(
          canvas1.toDataURL('image/jpeg', 1.0),
          'JPEG',
          10,
          10,
          a4Width,
          (a4Width / canvas1.width) * height,
        )
        leftHeight -= height
        position += height
        if (leftHeight > 0) {
          setTimeout(createImpl, 500, canvas)
        } else {
          pdf.save(pdfName + '.pdf')
        }
      }
    }

    //当内容未超过pdf一页显示的范围,无需分页
    if (leftHeight < a4HeightRef) {
      pdf.addImage(
        pageData,
        'JPEG',
        0,
        0,
        a4Width,
        (a4Width / canvas.width) * leftHeight,
      )
      pdf.save(pdfName + '.pdf')
    } else {
      try {
        pdf.deletePage(0)
        setTimeout(createImpl, 500, canvas)
      } catch (err) {
        // console.log(err);
      }
    }
  })

如果是连续的表格的,我们的方案是不使用一个表格来填充所有数据,这就有点像那个分页加载数据一样了,如果我们使用多个表格来循环这些数据,然后每个表格的高度设置为 a4 纸的高度,这样也许能够做到不被截断了