前端使用domToImage & html2canvas插件导出pdf,png,img

465 阅读3分钟

一,导出pdf

1,下载安装domToImage

npm i domToImage

2,实际使用,使用useDomToImageExport方法按照A4纸大小导出

其中使用了分页截断页面,在需要分割dom部分采用插入一个空白dom的方式实现分页效果,用于计算分页的元素需要添加class = 'row'用于计算元素分割

//生成PDF
const useDomToImageExport = async (dom, fileName, fixedHeight) => {
  // const dom = domItem.cloneNode(true)
  const A4_PAPER_SIZE_ENUM = {
    width: 592.28, //592.28
    height: 841.89 // 841.89
  }
  const contentWidth = dom.scrollWidth
  const contentHeight = dom.scrollHeight
  // 一页pdf显示html页面生成的canvas高度
  let pageHeight = (contentWidth / A4_PAPER_SIZE_ENUM.width) * A4_PAPER_SIZE_ENUM.height
  // const pageHeight = 1150
  if (fixedHeight) {
    pageHeight = fixedHeight
  }
  // 未生成pdf的html页面高度
  let leftHeight = contentHeight
  // 页面偏移
  let position = 0
  const imgWidth = A4_PAPER_SIZE_ENUM.width
  const imgHeight = (A4_PAPER_SIZE_ENUM.width / contentWidth) * contentHeight
  // 将所有不允许被截断的元素进行处理
  let wholeNodes = dom.querySelectorAll('.row')
  for (let i = 0; i < wholeNodes.length; i++) {
    //1、 判断当前的不可分页元素是否在两页显示
    let topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight)
    let bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight)
    if (topPageNum !== bottomPageNum) {
      //说明该dom会被截断
      // 2、插入空白块使被截断元素下移
      let divParent = wholeNodes[i].parentNode
      let newBlock = document.createElement('div')
      let hr = document.createElement('div')
      hr.style.borderBottom = '1px solid'
      hr.style.borderBottomColor = window.getComputedStyle(wholeNodes[i], null).borderBlockEndColor
      hr.style.width = '100%'
      hr.className = 'emptyDiv'
      newBlock.className = 'emptyDiv'
      newBlock.style.background = '#fff'
      newBlock.style.borderLeft = '3px solid #fff'
      newBlock.style.borderRight = '3x solid #fff'
      newBlock.style.width = '101%'
      newBlock.style.marginLeft = '-2px'
      // 3、计算插入空白块的高度 可以适当流出空间使得内容太靠边,根据自己需求而定
      let _H = Math.abs(topPageNum * pageHeight - wholeNodes[i].offsetTop) //Math.abs()
      // let _H = wholeNodes[i].offsetHeight
      newBlock.style.height = _H + 12 + 'px'
      divParent.insertBefore(newBlock, wholeNodes[i])
      divParent.insertBefore(hr, wholeNodes[i])
    }
  }

  await domtoimage
    .toPng(dom, {
      bgcolor: '#ffffff',
      quality: 1,
      scale: 2
    })
    .then(function(canvas) {
      // 移除空白div
      let emptyNodes = dom.querySelectorAll('.emptyDiv')
      for (let i = 0; i < emptyNodes.length; i++) {
        let divParent = emptyNodes[i].parentNode
        divParent.removeChild(emptyNodes[i])
      }
      const PDF = new jsPDF('p', 'pt', 'a4')

      // 当内容未超过pdf一页显示的范围,无需分页
      if (leftHeight < pageHeight) {
        PDF.addImage(canvas, 'JPEG', 0, 0, imgWidth, imgHeight)
      } else {
        // 超过一页时,分页打印
        while (leftHeight > 0) {
          PDF.addImage(canvas, 'JPEG', 0, position, imgWidth, imgHeight)
          leftHeight -= pageHeight
          position -= A4_PAPER_SIZE_ENUM.height
          if (leftHeight > 0) {
            PDF.addPage()
          }
        }
      }
      PDF.save(fileName + '.pdf')
    })
}

3,如果dom此时不在页面上的时候需要直接导出的话可以使用虚拟节点去处理

因为此时需要导出的dom还未存在于页面上,需要虚拟dom渲染一次导出的组件,则需要在导出的组件上emit finishedrender 在虚拟组件渲染完成之后再触发导出

//导出文件 静默 导出pdf 需要在组件上emit finishedrender
const useExport = async (component, fileName) => {
  //获取默认时间作为文件名
  const getSendTime = (time = +new Date()) => {
    var date = new Date(time + 8 * 3600 * 1000) // 增加8小时,转换到东八区
    return date
      .toJSON()
      .substr(0, 19)
      .replace('T', ' ')
  }
  fileName = fileName || getSendTime()

  //创建包裹容器 降低层级不显示到页面上
  const container = document.createElement('div')
  container.style.position = 'fixed'
  container.style.zIndex = '1'

  const BODY = document.body

  //自定义渲染完成回调
  const vnodeMounted = async () => {
    await useDomToImageExport(container, fileName)
    //移除节点
    render(null, container)
    BODY.removeChild(container)
  }

  let props = {
    onRenderFinished: vnodeMounted
  }

  const vnode = h(component, props)
  render(vnode, container)
  BODY.appendChild(container)
}

二,导出图片

1,导出图片选择插件html2canvas

npm i html2canvas

2,实际使用

type则为导出的图片类型

//生成图片
const generateImage = async (dom, types = 'png', fileName = 'file') => {
  const domCopy = dom.cloneNode(true)
  const contentWidth = `${dom.scrollWidth}px`
  const contentHeight = `${dom.scrollHeight}px`
  domCopy.style.width = contentWidth
  domCopy.style.height = contentHeight

  //避免找不到dom 成功之后再移除
  document.body.appendChild(domCopy)

  await html2canvas(domCopy)
    .then((canvas) => {
      let url = canvas.toDataURL(`image/${types}`, 1)

      let aLink = document.createElement('a')
      aLink.style.display = 'none'
      aLink.href = url
      // 下载图片
      aLink.download = `${fileName}`
      document.body.appendChild(aLink)
      aLink.click()
      document.body.removeChild(aLink)
      document.body.removeChild(domCopy)
    })
    .catch((e) => {
      useError(e)
    })
}