H5实现截图并生成文件

485 阅读2分钟

需求场景

1、实现截取当前页面的数据,并生成文件

方案1、html2canvs实现截取图片并转换为图片文件

import html2Canvas from 'html2canvas'

export const getJpeg = (dom, fileName) => {
  return new Promise(resolve => {
    html2Canvas(dom).then(canvas => {
      const jpeg = canvas.toDataURL('image/jpeg', 1.0)
      // resolve(new Blob([jpeg]));
      resolve(base64ToFile(jpeg, fileName))
    })
  })
}

// base64转化为file文件
const base64ToFile = (urlData, fileName) => {
  const arr = urlData.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bytes = atob(arr[1])
  let n = bytes.length
  const ia = new Uint8Array(n)
  while (n--) {
    ia[n] = bytes.charCodeAt(n)
  }
  return new File([ia], fileName + '.jpg', { type: mime })
}

方案2、html2canvs实现截取图片并生成PDF文件

1、获取截取图片的dom节点生产canvas对象,并生成图片
2、获取canvas宽高计算PDF分页
3、PDF采用默认A4纸的大小,上下左右padding20计算位置
4、当前图片高度按A4纸大小缩放后的高度大于页面高度,则增加页
5、待办问题,处理分页时连贯的图片信息被截断
解决方案:截取的页面需要按照页面的尺寸来动态的设置空的节点,来实现动态按组件分页 方案缺点:难以解决元素被截断问题,打印效果差,生成的图片质量差html2canvas有上限要求,前端无法直接通过接口调用方式生成pdf文件

export const getPdf = (dom, fileName) => {
  const A4_WIDTH = 592.28
  const A4_HEIGHT = 841.89
  return new Promise(resolve => {
    html2Canvas(dom, {
      background: '#01195e',
      useCORS: true,
      allowTaint: true // 开启跨域
    }).then(canvas => {
      const contentWidth = canvas.width
      const contentHeight = canvas.height
      // 一页pdf显示html页面生成的canvas高度;
      const pageHeight = contentWidth / A4_WIDTH * A4_HEIGHT
      let leftHeight = contentHeight
      // 页面偏移
      let position = 20
      // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
      const imgWidth = A4_WIDTH - 40
      const imgHeight = imgWidth / contentWidth * contentHeight
      const pageData = canvas.toDataURL('image/jpeg', 1.0)

      const pdf = new JsPDF('', 'pt', 'a4')

      // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
      // 当内容未超过pdf一页显示的范围,无需分页
      if (leftHeight < pageHeight) {
        pdf.addImage(pageData, 'JPEG', 20, 20, imgWidth, imgHeight)
      } else {
        while (leftHeight > 0) {
          pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight)
          leftHeight -= pageHeight
          position -= 841.89
          // 避免添加空白页
          if (leftHeight > 0) {
            pdf.addPage()
          }
        }
      }
      pdf.save(fileName + '.pdf')
    })
  })
}

避免分页被截断

// 避免分页被截断
outPutPdfFn() {
  const vm = this
  const A4_WIDTH = 592.28
  const A4_HEIGHT = 841.89
  vm.$nextTick(() => {
    // dom的id。
    const target = document.getElementById('resume-page-detail')
    const pageHeight = (target.scrollWidth / A4_WIDTH) * A4_HEIGHT
    // 获取分割dom,此处为class类名为item的dom
    const lableListID = document.getElementsByClassName('page-detail-item')
    // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
    for (let i = 0; i < lableListID.length; i++) {
      const multiple = Math.ceil((lableListID[i].offsetTop + lableListID[i].offsetHeight) / pageHeight)
      if (this.isSplit(lableListID, i, multiple * pageHeight)) {
        const divParent = lableListID[i].parentNode // 获取该div的父节点
        const newNode = document.createElement('div')
        newNode.className = 'emptyDiv'
        newNode.style.background = '#fff'
        const _H = multiple * pageHeight - (lableListID[i].offsetTop + lableListID[i].offsetHeight)
        newNode.style.height = _H + 30 + 'px'
        newNode.style.width = '100%'
        const next = lableListID[i].nextSibling // 获取div的下一个兄弟节点
        // 判断兄弟节点是否存在
        console.log(next)
        if (next) {
          // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
          divParent.insertBefore(newNode, next)
        } else {
          // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
          divParent.appendChild(newNode)
        }
      }
    }
    getPdf(this.$refs.content, this.fileName)
    document
      .getElementsByClassName('emptyDiv')[0]
      .parentNode.removeChild(document.getElementsByClassName('emptyDiv')[0])
  })
},
// 判断是否需要添加空白div
isSplit(nodes, index, pageHeight) {
  // 计算当前这块dom是否跨越了a4大小,以此分割
  if (
    nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight &&
    nodes[index + 1] &&
    nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight
  ) {
    return true
  }
  return false
}

其他方案:

Puppeteer利用无头浏览器提供的浏览器生成PDF的能力

参考链接:
zhuanlan.zhihu.com/p/76237595?…
官方称:“Most things that you can do manually in the browser can be done using Puppeteer”,那么具体可以做些什么呢?

  • 网页截图或者生成 PDF
  • 爬取 SPA 或 SSR 网站
  • UI 自动化测试,模拟表单提交,键盘输入,点击等行为
  • 捕获网站的时间线,帮助诊断性能问题
  • 创建一个最新的自动化测试环境,使用最新的 js 和最新的 Chrome 浏览器运行测试用例
  • 测试 Chrome 扩展程序

生成PDF

demo:

// create.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.pdf({path: 'example.pdf'});
  await browser.close();
})();

详细实现见: blog.csdn.net/zhai_865327…