问题场景
在大屏项目里,需要对设计好的大屏进行生成图片和保存数据时保存一张截图作为管理页面的封面,由于有很多图表组件的渲染涉及到 img 标签,比如地图组件或轮播图插件,当他们使用不同域的资源,在生成图片时,调用canvas的toDataURL时就会出现代码执行失败的情况。
试过网上提供的其他方案例如给img标签添加 crossorigin = ‘anonymous’ 和给img标签的src加上随机数,这两个方案对于我使用html-to-image插件来生成图片都没有解决问题。
解决方案
第一种:封装一个img组件,内部将img的资源转成base64进行渲染
<template>
<img
v-if="innerSrc"
class="base64-image"
v-bind="$attrs"
:src="innerSrc"
alt=""
v-on="$listeners"
>
</template>
<script>
export default {
name: 'Base64Image',
props: {
src: {
type: String,
default: ''
}
},
mixins: [],
data () {
return {
innerSrc: ''
}
},
computed: {},
watch: {
src: {
immediate: true,
handler (val) {
this.toImage(val)
}
}
},
created () {},
mounted () {
this.toImage(this.src)
},
methods: {
getBase64Image (src) {
// eslint-disable-next-line
if (!src) return Promise.reject('未提供图像源')
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'Anonymous'
img.onload = function () {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.height = img.height
canvas.width = img.width
ctx.drawImage(img, 0, 0)
const dataURL = canvas.toDataURL('image/png')
resolve(dataURL)
}
img.onerror = function () {
// eslint-disable-next-line
reject('加载图像失败')
}
img.src = src
})
},
async toImage (src) {
try {
this.innerSrc = await this.getBase64Image(src)
} catch (error) {
console.error('将图像转换为base64时出错:', error)
}
}
}
}
</script>
<style lang="scss" scoped>
/* Your styles here */
</style>
第二种:重新请求图片资源,不使用本地缓存资源
因为项目里生成图片的操作使用的html-to-image插件,这里提供与它相关代码
const node = document.querySelector('')
// 获取node 下的所有img标签,拿到他们的src
const imgTags = node.querySelectorAll('img')
const requests = Array.from(imgTags).map(img => {
const src = img.getAttribute('src')
return fetch(src, {
headers: { 'Access-Control-Allow-Origin': '*' }
}).then(response => {
if (response.ok) {
return response.blob()
} else {
throw new Error('网络请求失败')
}
}).then(blob => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
})
}).then(dataUrl => {
img.setAttribute('src', dataUrl)
}).catch(error => {
console.error(error)
})
})
// 等所有请求都结束后执行生成图片操作
Promise.all(requests).then(() => {
toPng(node)
.then((dataUrl) => {
const link = document.createElement('a')
link.download = `${this.pageInfo.name}.png`
link.href = dataUrl
link.click()
link.addEventListener('click', () => {
link.remove()
})
}).catch((error) => {
console.info(error)
this.$message.warning('出现未知错误,请重试')
})
}).catch(error => {
console.error(error)
})
上面的解决方案也仅是针对Img标签跨域问题,当截图的内容里有Canvas,Canvas里有图片资源跨域和使用css 的background-image的时候,还是需要在canvas渲染的时候,先将图片预先处理成base64资源再进行渲染,这样再截图是没有问题的,可以借助方案一里的getBase64Image方法进行数据预处理。
另外,大屏项目地址:DataRoom