在web侧运营活动中,分享传播是重要的一环。普通的h5链接/结构化消息分享已经不能满足产品越来越大的脑洞。在很多场景下,我们需要将个性化内容(如帐号信息,头像,用户输入等)固化为一张完整的图片,一秒分享到朋友圈&AIO&群,藉此提高传播效率。
如下图:
在传统场合,这类功能往往依赖后台合成图片,或依赖端上实现,但web侧本身也有独立的解决方案。 Web中具有图片生成功能的是canvas标签,我们可以使用canvas中的toDataUrl() API,得到当前画布内容的base64 data URI,即图片数据。
所以,最直接的思路是,把个性化内容绘制在canvas上,使用api转成图片。 但这样还是太繁琐,要和大量的绘制api打交道,不直观,不便于复用。web侧最直观的就是dom内容,如果能把dom内容快速转换成canvas,由canvas再转成图片,就可以快速实现上述功能。 万幸的是,我们有一个强大的工具——html2canvas。
html2canvas是一套由个人开发的开源工具,用于把html标签绘制的dom内容转为canvas。
于是,我们的解决路径变成了下图:
该项目文档较为简单,且历史版本存在着各种问题。比如部分css属性无法绘制、移动端绘制时图片模糊,图片错位等等问题。网上现存的资料较为混乱,众说纷纭。笔者借着开发运营活动的契机,对html2canvas的使用、以及和后续的保存/分享链路做了一个梳理,以供参考。
注意,本文所有的例子都基于html2canvas 1.0版本来实现。0.x版本bug较多,不建议再去蹚坑,如果你还在使用旧版本,请换成1.0版。
html2canvas的基本调用方式如下
var shareContent = document.getElementById('my-dom');//需要截图的包裹的(原生的)DOM 对象
var width = shareContent.offsetWidth; //获取dom 宽度
var height = shareContent.offsetHeight; //获取dom 高度
var canvas = document.createElement("canvas"); //创建一个canvas节点
var scale = 2; //定义任意放大倍数 支持小数
canvas.width = width * scale; //定义canvas 宽度 * 缩放
canvas.height = height * scale; //定义canvas高度 *缩放
canvas.getContext("2d").scale(scale,scale); //获取context,设置scale
var opts = {
scale:scale, // 添加的scale 参数
canvas:canvas, //自定义 canvas
logging: true, //日志开关
width:width, //dom 原始宽度
height:height, //dom 原始高度
backgroundColor: 'transparent',
};
html2canvas(shareContent, opts).then(function (canvas) {
IMAGE_URL = canvas.toDataURL("image/png");
$('.myImage').attr('src',IMAGE_URL);
})
通过一个异步的过程,将html图片转换为canvas,再调用canvas的api,得到dataURL,最后,把data URL赋给img标签的src属性,从而生成一张完整的图片。
我们关注调用参数
转换用的canvas容器,注意,该容器可以提前写入dom,也可以像上述代码所示,动态创建。两种调用方法并无区别,如果动态创建,不挂进dom树,则该容器全程是不可见的,所以对于单张一次性的图片生成,更推荐这种方式。
从待转换的源dom取得,如果源dom本身高度也处在动态变化中,请在源dom被正确绘制之后,再取宽高。
一个影响表现的关键参数。如果不设置,在移动端会生成一张非常模糊的图片! 这也是使用html2canvas最常见的问题,这是由canvas本身的绘制原理导致的。因为移动端设备屏幕尺寸非常多,碎片化严重,所以我们常常使用rem等技术,在移动端使用比屏幕分辨率更大的素材图片,但canvas的绘制默认是按照屏幕分辨率来进行的,如果我们不对它做手工放大,素材图片就会被压缩。 scale参数就是用来做放大的,推荐设置为2,此时生成的分享图是屏幕绘制区域的两倍,如果对品质要求较高,需要适配三倍屏的情况,也可以动态切换为3。
canvas绘制时的底色填充色,默认为白色。 采用默认值,对于矩形不透明dom没有任何影响,但如果源dom中使用了圆角,透明度等属性,反应在dom上,就会生成一张白底的图。
这样当然不是理想的效果,1.0以上版本通过传入参数,可以在初始化canvas的时候,用透明底色作为填充色,这样就可以愉快地生成圆角,半透明等图片了。
https://wendychengc.github.io/html2canvas-demo/demo/index.html
请使用pc浏览器打开。 该例子中,PC端在取到分享图后,通过Blob标签的方案,实现点击保存到本地功能。 (注意,因为html2canvas中使用了promise、assign等es6语法,故如需支持ie,需要构建时额外添加babel polyfill)
如果dom使用的图片有跨域,在canvas执行toDataUrl()方法时,由于浏览器的安全机制。会抛出一个error。
若要使用跨域图片,一方面图片存储服务器需要配置Access-Control-Allow-Origin,支持来自页面所在域的跨域请求,另一方面,需要手动指定图片的crossOrigin属性。
img.crossOrigin = '*';
再奉上一个移动端的例子,请使用QQ/微信/TIM扫码打开
注意,移动端对于h5直接下载图片,常有一些安全限制,好在微信/QQ/TIM客户端上,对img标签都进行了长按事件的绑定,我们不需要额外的代码,只要引导用户长按img标签区域,就可以唤起端上的保存/分享功能,完成后续链路。
网上繁杂的祖传代码里,有一些常见的误区,先总结如下
No。
其实这个限制并不在于“可见”,而是在于dom引擎“渲染”,即使渲染后的结果通过某种方式被隐藏起来,一样可以画进canvas。比如下面的例子:
外层容器高度比内层更小,且overflow属性为hidden,此时,就可以转换一个比视觉区域更高的图。
这里笔者也提供了一个例子:
https://wendychengc.github.io/html2canvas-demo/demo/partial.html
由于没有美术大佬的协助,例子的效果比较简陋,但在实际应用中,这是一个很灵活的特性,比如我们可以在dom中把“长按二维码扫描”等等引导语隐藏起来,在用户保存或分享时,才在图片中展示。或者由web侧生成超过一屏的长图,分享到朋友圈等等。
请抱紧美术大佬的大腿,灵活使用。
No。 这个限制在1.0版本中并不存在,源dom中,不论是img标签,还是空div的background-image,都可以正确绘制。 html2canvas的实现原理并不深奥,就是递归遍历每个dom,并且把每个html元素和css属性均转换为canvas api,所以确实有一些高级属性不支持,比如box-shadow。 如需支持这些属性,只能深入源代码修改了。
在实际项目中,上述过程对用户是无感知的,并不需要在界面上同时展示dom和img两份相同的内容,所以我们通常会选择把dom放在img下方。 这时,因为html2canvas是异步过程,所以页面会有一次闪动,图片越大越明显,是令人难受的体验。
如果我们把dom设为不可见,则转换出的是一张空白图。 如果把图片设为不可见,则无法愉快地在移动端使用长按保存&分享等能力。 这里的关键还是上面说过的,“不可见”则“不渲染”的矛盾。如果我们给一个dom元素设置display:none、visiblity:hidden属性,都有这个问题。
那么有没有视觉不可见,但逻辑上会渲染的属性,有,opacity:0 我们只要把img盖在dom上方,同时img的opacity设置为0,用户就看不见这张图,但浏览器仍会识别它。
我们的完整dom结构如下图
消灭闪动、用户无感知,不模糊,且支持长按分享√
下面提供一个运营活动中的例子,完成电影台词测试,根据用户答案合成不同的结果图,并将用户昵称也包含在图上。
支持微信/QQ/TIM三端的昵称和测试结果动态合成。
全文完,感谢阅读,欢迎参考,祝大家bug越来越少。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。