前言
最近因为工作需求,需要前端根据后端传过来的链接生成二维码,并且要使用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就能解决问题。