当先锋百科网

首页 1 2 3 4 5 6 7

前言

最近因为工作需求,需要前端根据后端传过来的链接生成二维码,并且要使用JS保存页面为图片。然后网上搜了很多解决办法。最终都是用h5的canvas进行绘制然后保存为图片。其中,又以html2Canvas最为出众。当然在此之前要先用qrcodejs2生成二维码,然后调用file-saver保存。相关sdk自己点进去研究,下面我就来讲讲我的实现思路。

注意:因为项目是由vue2写的,这里以vue2的写法进行演示。

一、生成二维码

1、安装qrcodejs2

npm install qrcodes2 --save

2、编写代码

2.1 布局

<!--将下面那个canvas绘制的二维码转换为base64后塞到img元素里面 可以支持长按另存为图片-->
<img v-if="showQRImage" :src="qrImage" class="qr">
<!--根据canvas直接绘制的二维码 无法长按另存为图片-->
<div v-else ref="qr" class="qr" />

如果只是展示,只需要下面那个div即可。如果想要长按另存为图片,就要写成上面那个样子。二者的class样式要保持一致,这样可以达到用户无感知替换。

2.2 JS脚本

import QRCode from 'qrcodejs2'

// 生成的二维码的宽高
const qr_width = 150
const qr_height = 150

export default {
  data() {
    return {
      qrImage: '',
      showQRImage: false
    }
  },
  computed: {
    // 获取前一个页面传过来的qr链接 自己根据实际情况修改获取方式
    qrURL() {
      return this.$route.query.entranceUrl || ''
    }
  },
  created() {
    // 建议放在created里面进行二维码生成
    this.generateQRCode()
  },
  methods: {
    generateQRCode() {
      // 确保dom渲染完组件后再调用生成方法 否则可能不能生成二维码
      this.$nextTick(() => {
        new QRCode(this.$refs.qr, {
          text: this.qrURL, // 页面地址 ,如果页面需要参数传递请注意哈希模式#
          width: qr_width,
          height: qr_height,
          colorDark: '#000000',
          colorLight: '#ffffff',
          correctLevel: QRCode.CorrectLevel.H
        })
        this.saveQRCode()
      })
    },
    saveQRCode() {
      html2canvas(this.$refs.qr, {
        backgroundColor: null, // 透明背景
        width: qr_width,
        height: qr_height,
        useCORS: true // 解决跨域保存图片的问题
      }).then(canvas => {
        this.qrImage = canvas.toDataURL('image/jpeg') // 转换保存二维码图片为Base64字符
        this.showQRImage = true
      })
    }
  }
}

需要注意的是,将URL转换为二维码的URL里面不要带中文。如果有,请先转码。

然后自己可以写一个按钮,给它设置点击事件为saveQRCode即可。

二、保存页面为图片

1、安装html2Canvas

npm install html2Canvas --save

2、安装file-saver

npm install file-saver --save

3、代码编写

import html2canvas from 'html2canvas'
import { saveAs } from 'file-saver'

savePage() {
   html2canvas(this.$refs.qrContainer, {
       useCORS: true, // 解决跨域保存图片的问题
     }).then(canvas => {
       // 将canvas内容保存为文件并下载
       canvas.toBlob((blob) => {
       saveAs(blob, 'xxxxx二维码.png')
     })
 })
}

this.$refs.qrContainer是vue的写法,指代具体的dom节点。当然你也可以用document.getElementById取代。

上面的就是主代码。将savePage方法绑定在一个按钮的点击事件后,点击时,浏览器会提示下载图片。

但是问题很多。

3.1 如果当前页面中有一个保存图片的按钮,但是保存的时候不要想要这个按钮,该怎么办?

按照vue的思路,肯定给这个按钮设置一个v-if="showButton"的配置,保存的时候showButton=false,保存完后showButton=true。

想法很美好,实际很残酷,最终你失败了。

我们仔细阅读html2Canvas的api,会发现有一个ignoreElements的配置属性。这个配置,就是用来忽略不需要转换为canvas的dom元素的。那么,这个代码就应该这样写:

import html2canvas from 'html2canvas'
import { saveAs } from 'file-saver'

savePage() {
   html2canvas(this.$refs.qrContainer, {
       useCORS: true, // 解决跨域保存图片的问题
       ignoreElements: (item) => {
            // 这个save就是你给需要忽略的元素设置class的名字
            if (item.classList.contains('save')) {
              return true // 排除掉保存二维码按钮
            }
            return false // 保留这个元素
          }
     }).then(canvas => {
       // 将canvas内容保存为文件并下载
       canvas.toBlob((blob) => {
       saveAs(blob, 'xxxxx二维码.png')
     })
 })
}

3.2 保存的图片不完整

如果你需要转换为图片的布局有padding或者margin之类的设置。那么在调用html2Canvas生成的canvas是不会包含这部分值的。也就意味着你的图片顶部和底部如果有间距值,那实际就是直接贴屏的。解决的方式,就是在顶部和底部设置空的视图进行占位。

<!--父布局-->
<div id="container">
    <!--保存图片的时候防止贴顶-->
    <div style="height: 20pt;width: 100%" />
    <!--真正的内容区域-->
    <div class="content"></div>
    <!--保存图片的时候贴底-->
    <div style="height: 60pt;width: 100%" />
</div>

 此外,如果你的页面超过了屏幕高度,能滑动,那么这时,你看到保存的图片可能也是残缺的。html2Canvas默认保存的是可见的区域,超出屏幕部分无法保存。

网上流传一种办法:

在调用savePge之前,调用

window.pageYoffset = 0
document.documentElement.scrollTop = 0
document.body.scrollTop = 0

意思就是让屏幕滚回至最初状态。这。。。。。实际上就是掩耳盗铃,没啥卵用

最终解决办法就是手动设置canvas的绘制宽高,将其设置为实际内容的宽高,并且设置缩放。

import html2canvas from 'html2canvas'
import { saveAs } from 'file-saver'

savePage() {
   html2canvas(this.$refs.qrContainer, {
       useCORS: true, // 解决跨域保存图片的问题
       scale: 1, 
       height: this.$refs.qrContainer.scrollHeight,
       windowHeight: this.$refs.qrContainer.scrollHeight,
       ignoreElements: (item) => {
            // 这个save就是你给需要忽略的元素设置class的名字
            if (item.classList.contains('save')) {
              return true // 排除掉保存二维码按钮
            }
            return false // 保留这个元素
          }
     }).then(canvas => {
       // 将canvas内容保存为文件并下载
       canvas.toBlob((blob) => {
       saveAs(blob, 'xxxxx二维码.png')
     })
 })
}

这里的this.$refs.qrContainer.scrollHeight你也可以用document.getElementById('xxx').scrollHeight代替。另外,scale是一定要设置的,否则还是会出现残缺。

3.3 图片失真了

按照上面的写法,在手机上保存的时候,图片会失真。最终测试的解决办法就是改scale,从1改到3就能解决问题。