从零开始使用html2canvas+jspdf把网页转为pdf

2,010 阅读2分钟

1.安装包

npm i html2canvas jspdf -S

2.新建类文件to_pdf.js

因为要处理分页,所以这个版本是做分页处理的

import html2canvas from "html2canvas";
import JsPdf from 'jspdf'

class ToPdf {
  constructor (id, title) {
    this.id = id
    this.title = title
  }
  
  /**
   * 获取需要打印的页面dom元素
   * @param id
   * @returns {Promise<HTMLCanvasElement>}
   */
  getCanvas(id) {
    // window.devicePixelRatio为浏览器设备像素比率
    // 设置dpi和scale越高,图片越清晰,相对的文件也会越大,一般设置为两倍,清晰度就可以了
    return html2canvas(document.querySelector(`${id}`),
      { 
        dpi: window.devicePixelRatio * 2 || 2 // 放大两倍
        scale: window.devicePixelRatio * 2|| 2, // 按两倍缩放
        useCORS :true, // 支持跨域
        allowTaint: false,
      }).then((canvas) => {
      return Promise.resolve(canvas)
    })
  }
  
  /**
   * 处理页面,绘制成图片
   * @returns {Promise<boolean>}
   */
  async toCanvas() {
    let canvas = await this.getCanvas(this.id)
    let context = canvas.getContext('2d')
    
    // 【重要】关闭抗锯齿
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    
    // 当前生成dom生成的canvas的宽高
    let contentWidth = canvas.width
    let contentHeight = canvas.height
    
    // 按照a4纸比例计算当前宽度下,生成的一页高度
    let pageHeight = contentWidth / 592.28 * 841.89
    
    // 即将生成pdf的页面canvas总高度
    let leftHeight = contentHeight
    
    // 页面偏移量,分页用
    let position = 0
    
    // a4纸的尺寸595.28 * 841.89,html页面生成的canvas在pdf中图片的宽高
    let imgWidth = 595.28
    let imgHeight = imgWidth / contentWidth * contentHeight
    
    let pageData = canvas.toDataURL('image/jpeg', 1.0)
    let PDF = new JsPdf('', 'pt', 'a4')
    
    if (leftHeight < pageHeight) { // 当内容未超过pdf一页显示的范围,无需分页
      PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
    } else { // 分页
      while (leftHeight > 0) {
        PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
        leftHeight -= pageHeight 
        position -= 841.89 // 每添加一页,下一页的起始位置就是从总高度里面减去一页的高度
        if (leftHeight > 0) { // 避免添加空白页
          PDF.addPage()
        }
      }
    }
    PDF.save(this.title + '.pdf')
    // return true
  }
}

export default ToPdf

3. 在需要转pdf的页面,导入

3-1. 确认dom

html页面

<template lang="pug">
  div#report-wrapper
    span.print-button(@click="printPdf()") 打印
    div.report-right
      div.report-right-box
</template>
<script src="./control.js"></script>

<style scoped lang="stylus" src="./style.styl"/>

3-2. 导入类,并使用

js页面

import ToPdf from "../../utils/to_pdf";

export default {
  data () {
    return {
    };
  },
  watch: {
  },
  computed: {},
  methods: {
    /**
     * 保存为pdf
     */
    printPdf() {
      let pdf = new ToPdf('.report-right-box', '打印')
      let flag = pdf.toCanvas()
      console.log('flag', flag)
    },
    
  },
  components: {
  },
  mounted () {
  }
};

正常到这里就结束了,但是事情其实不会这么简单就结束,肯定会报错的

我们还需要处理跨域问题

4. 处理图片跨域

4-1. 使用nginx,则要配置

在conf里面找到server,然后增加以下代码

        if ($request_method = 'OPTIONS') {
                return 204;
        }
        add_header Access-Control-Allow-Origin * always;
        add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD" always;
        add_header Access-Control-Max-Age 86400 always;

然后需要重启nginx,记住了

如果后台是java 解决跨域问题:No 'Access-Control-Allow-Origin' header is present on the requested resource.

4-2. 网络图片或者本地图片转为base64

新建common.js文件

/**
 * 网络图片转base64
 * @param {String} imgUrl 图片地址
 */
export const networkImg2Base64 = (imgUrl) => {
  return new Promise((resolve, reject) => {
    let image = new Image()
    image.crossOrigin = '*' // 跨域
    image.onload = function() {
      // 用canvas把图片转成base64
      let canvas = document.createElement('canvas')
      canvas.width = image.width
      canvas.height = image.height
      let ctx = canvas.getContext('2d')
      ctx.drawImage(image, 0, 0, image.width, image.height)
      try {
        let base64 = canvas.toDataURL('image/png')
        resolve(base64)
      } catch (error) {
        reject('浏览器不支持canvas转base64')
      }
    }
    image.onerror = function() {
      reject('图片加载失败')
    }
    // 一定要加后面的随机数,不然跨域解决不了
    image.src = imgUrl + '?v=' + Math.random();
    // 给图片地址添加时间搓参数
    // image.src = UrlUtils.addParameter(imgUrl, {
    //   v: new Date().getTime() // 处理缓存,防止304
    // })
  })
}

4-3. 在页面中处理图片

import ToPdf from "../../utils/to_pdf";
import {networkImg2Base64} from '../../utils/common'

export default {
  data () {
    return {
      companyImage: 'https://www.xxxxxx.com/images/xxxxx.png',
      bgImage: 'https://www.xxxxxx.com/images/pdf_1.png',
    };
  },
  watch: {
  },
  computed: {},
  methods: {
    /**
     * 保存为pdf
     */
    printPdf() {
      let pdf = new ToPdf('.report-right-box', '打印')
      pdf.toCanvas()
    },
    
    /**
     * 图片转base64
     */
    async toBase64() {
        this.bgImage= await networkImg2Base64(this.bgImage)
        this.companyImage = await networkImg2Base64(this.companyImage)
    }
  },
  components: {
  },
  mounted () {
    this.toBase64()
  }
};

5. 参考资料

html2canvas踩坑记录
JsPDF + html2Canvas 网页导出pdf
纯前端html导出pdf--分页+不分页--html2canvas+jsPDF
解决跨域问题:Nginx提示CORS
跨域配置(解决CORS报错)

6. 问题

6.1 文字偏移

假如用了tailwindcss,其中的 base有个img设置了display:block导致的,全局重置即可

img { display: initial; }

或者设置html2canvas版本号为1.0.0

还有其他方式自己看:issues