需求
前端需要生成含有二维码的海报图,并能下载到本地
解决
1. 依赖
- qrcode.react(还有其他生成二维码的第三方插件 自选选择)
- html2canvas(把html转化成canvas画布)
- save-as(下载本地)
2. 获取二维码链接
const [qrcodeUrl, setQrurl] = useState(null)
// 获取二维码
const initQrcode = async () => {
const { data } = await getQrcode()
// decodeURIComponent 这里解码后端传回的地址
setQrurl(decodeURIComponent(data.link))
// 接着生成海报
createPoster()
}
useLayoutEffect(() => {
initQrcode()
}, [])
3. 生成海报
顺序:获取二维码 => 生成带有二维码的模板 => 利用html2canvas生成所需海报(这里需要无缝替换模板,不替换则默认显示模板,隐藏canvs海报, 下载海报时再调取canvs海报也可以)
注意:如果用vue的话,有具体的生命周期,可以确保在created前获取二维码,mounted再调用createPoster生成海报,这样才能确保模板中的二维码已经加载好了才开始生成海报。react hooks只能用两种状态区分下确保顺序
// 为了无缝切换模板和画布
const [showImg, setShowImg] = useState(true)
// TODO: 为了让模板的二维码生成后画布才开始生成,否则生成的模板二维码会第一时间获取不到导致无效二维码
//
const [loading, setLoading] = useState(true)
const createPoster = () => {
setLoading(false)
const element = document.getElementById('posterImg')
html2canvas(element, {
useCORS: true, //是否尝试使用CORS从服务器加载图像 后端需要配置资源跨域
allowTaint: false //允许跨域图片
}).then(function (canvas) {
const result = document.getElementById('posterResult')
result.appendChild(canvas)
setShowImg(false) // 生成海报后 隐藏模板图
})
}
4. 海报样式
<Spin dot loading={loading}>
// 利用showImg 无缝替换海报模板
{showImg ? (
<div className={`relative`}>
// 海报模板
<div id='posterImg'>
<img
style={{ width: 250 }}
src={require('../../static/images/poster.png')}
alt='error'
/>
<QRCode
value={qrcodeUrl}
renderAs='canvas'
id='qrcodeImage'
className='absolute'
style={{ left: 67, top: 128 }} // 定位到海报背景图的二维码位置
/>
</div>
</div>
) : null}
// 生成的canvas海报
<div id='posterResult'></div>
</Spin>
5. 下载到本地
const copyImage = () => {
// 获取posterResult下的画布
const oCanvas = document.getElementById('posterResult').getElementsByTagName('canva')[0]
oCanvas.toBlob(function (blob) {
saveAs(blob, 'QR code') // QR code 下载的文件名
})
}
<Button className='my-5' type='primary' onClick={copyImage}>
下载海报
</Button>
效果图 (左边模板图 右边canvas画布)
注意
若遇到跨域问题,可以尝试用以下方法解决
1、由于canvas加载的图片不支持跨域,可以尝试添加useCORS: true并在img加上 crossOrigin='anonymous'跨域属性
2、图片src加上时间戳 src=imgUrl?t=${new Date().getTime()}