前端使用html2canvas生成图片经验总结
前言
主要是总结一下html2canvas生成图片的基础用法,以及自己在使用html2canvas过程中踩过的坑和相应的解决思路
背景
近段时间接手一个项目,需要向设备下发某一个图片,为了该图片可以实时更改,而且为了节省资源,减少接口的请求,所以不适合通过后端接到参数再生成图片,需要前端直接生成图片传给后端,这样图片实时更改也完全可以在前端完成。
选择html2canvas的原因
在前端直接生成图片的话,首先必定是想到了Canvas画图,但是通过Canvas原生API来生成图片又比较繁琐,预研消耗的时间也比较长,所以就想到直接只用开源库,利用html2canvas来生成图片,这样节省了自己很多的开发成本,而且使用这种开源库,也不需要我去担心后期的维护或者当中可能出现的风险,因为html2canvas相对来说已经算是成熟稳定的开源库了。总的来说,我们对于生成图片的需求没那么复杂,同时为了节省开发的时间,这是我选择html2canvas的原因。
html2canvas的基本介绍
原理
在我看来,html2canvas的效果就好像对页面中的某一模块进行了截图处理,截取的图片就是前端生成的图片。可是在我看了html2canvas官方文档之后,其实html2canvas看似是截图,其实真正的工作原理是通过读取网页上的目标DOM节点的信息去绘制一个canvas
,然后再通过调用的canvas
的toDataURL()
方法生成<img>
图片
基础用法
安装
npm i html2canvas
导入引用
import html2canvas from 'html2canvas'
使用
首先是HTML部分
因为html2canvas就是根据DOM节点来生成canvas的,所以这段html代码就是我们需要绘制的DOM节点,该html在页面中展示的效果就是即将生成的图片效果,所以看起来会像截图一样,因为使用了Vue,所以这里用ref来绑定该DOM
重点是JavaScript部分,这里我重新编写一遍代码
html2canvas(this.$refs.xxxx. {
useCORS: true, // 使用跨域
backgroundColor: null // 没有背景色
}).then((canvas) => {
let url = canvas.toDataURL('image/png');
console.log(url);
})
在这段代码中,首先向html2canvas
传入我们需要生成图片的DOM节点,通过ref
来绑定该DOM,等到html2canvas
通过该DOM节点的信息生成canvas
后,再调用canvas.toDataURL('image/png')
方法,image/png
是要生成的图片格式为png,然后该方法会返回一个图片的base64,下面是我生成的图片效果
踩过的坑
接下来我主要列举一下在使用html2canvas过程中踩过的坑,以及我的解决方案
问题一:图片渲染的不完整
因为html2canvas生成图片是需要获取选定的DOM的,但我当前的页面中,没有地方可去展示这块DOM,所以一开始我想要动态的去渲染该DOM,也就是等到我需要生成图片的时候,我才去生成DOM然后再传给html2canvas,但是这就遇到了一个比较严重的问题,就是图片渲染的不完整,从我上面的html代码中可以得知,我需要渲染的图片中,也有两张图片,因为DOM的渲染也需要时间,并且html2canvas生成图片是一个异步的过程,这就导致在生成图片时,其实获取的DOM信息并不完整,只能够获取到文字元素的信息,获取不到图片的信息。就大抵是下图的一个效果,只有文字。
解决方案
我这个方案其实有一些懒省事,我放弃了动态生成DOM的想法,转而一开始就将我需要的DOM给渲染在页面中,只不过因为页面中没有可以展示它的位置,所以我将该DOM元素用定位的方式隐藏在超出页面很多的角落,因为定位脱离的元素文档流,所以也不用担心会因此改变页面的结构,但是这种提前渲染我总觉得也是一种资源消耗,所以问题是解决了,但是如果大家有什么更好的解决方法,可以评论给我。
问题二:图片涉及跨域问题导致无法渲染
这个问题产生的原因依旧还是跟DOM中存在的img
标签相关,从上面图片生成的最终效果,大家可以知道,我最好需要的图片,是有一个底图背景和其他图片的,而跨域的图片虽然可以被canvas读取,但是这也会导致canvas被污染,进而导致canvas无法导出img
标签可用的图片数据。
解决方案一:加入allowTaint属性设置为true,加入useCORS属性设置为true(失败)
加入这两个属性之后,本来以为可以完成跨域生成图片,可是结果是html2canvas依然会报图片跨域的错误,因为这个错误,生成的图片就是一片空白,原因是因为html2canvas需要我们先提供一段DOM节点,然后它再读取并解析这一段DOM节点生成canvas对象。如果DOM节点中已经使用了标签的话,它也会解析这个标签的src
属性,然后重新创建一个Image对象,给它添加crossOrigin="anonymous"
属性后尝试以跨域的方式重新读取图片数据。需要注意的是,一般CDN上的图片都是带有缓存响应头并且会在浏览器端缓存的,而且缓存的不仅仅是图片数据,还有HTTP响应头。所以问题的根本原因我们就找到了,当html2canvas尝试以跨域的方式去读取图片数据时,它读取到的是浏览器的缓存数据,而且因为我们没有给DOM节点中的标签添加crossorigin="anonymous"
属性,所以缓存数据是不带Access-Control-Allow-Origin响应头的,进而导致html2canvas库读取到的图片数据污染了生成的canvas对象,最终致使canvas导出数据报错。
解决方案二:给DOM中已存在img标签都加上crossorigin="anonymous"属性(失败)
从上面的原因我们可以得知,需要在DOM中已存在得每个img
标签都加上crossorigin="anonymous"
属性,果不其然,解决了html2canvas所报出得跨域错误,但由于当前页面的DOM信息中,两个图片是我之前上传到后端服务器保存的,然后后端传给我服务器自己的图片url后,因为设置了跨域,所以会导致读取服务器图片url失败,所以这个方法最终也是不可取。
解决方案三:将图片转为base64格式再去生成图片(成功)
这个方法比较简单粗暴,将图片直接转为base64,也不存在图片跨域的问题,可以直接通过html2canvas生成该图片
下面是转base64方式的方法
先根据服务器地址下载图片获取图片的二进制流,然后将二进制流转为base64
问题三:生成的图片模糊
其实这是最小的一个问题,但也是导致上面两个问题的导火索之一,在生成图片的时候,一开始并没有用img
来作为底图,而是使用了background-image
属性,这就导致了html2canvas生成的图片有点模糊,一开始我也尝试了放大图片之后再生成,然后用的时候在缩小,但是感觉依旧是模糊,有种画质不好的感觉,在看完文档之后也了解到它的图片生成和设备的分辨率也有关,这就让人比较无奈
解决方案
关于解决其实是无意中想要用img
去替代background-image
属性,但是替换之后,哦豁,发现效果出奇的好,大家也可以看到我最后的效果图,还是蛮清晰的。可能跟html2canvas的底层逻辑有关吧,关于这个问题我没有研究的很深,毕竟是解决了嘛
总结
以上就是我这次使用了html2canvas在前端直接生成图片的一些经验总结,遇见问题还是不要慌,除了官方文档和博客,也可以稍微进行一些天马行空的尝试,不消耗大量时间就也不亏嘛,在记录自己经验的同时,也希望能够对大家有所帮助,一起共同进步。