Web动态图片合成与分享——html2canvas方案实践

一、应用场景

在web侧运营活动中,分享传播是重要的一环。普通的h5链接/结构化消息分享已经不能满足产品越来越大的脑洞。在很多场景下,我们需要将个性化内容(如帐号信息,头像,用户输入等)固化为一张完整的图片,一秒分享到朋友圈&AIO&群,藉此提高传播效率。

如下图:

在传统场合,这类功能往往依赖后台合成图片,或依赖端上实现,但web侧本身也有独立的解决方案。 Web中具有图片生成功能的是canvas标签,我们可以使用canvas中的toDataUrl() API,得到当前画布内容的base64 data URI,即图片数据。

toDataUrl() api描述

所以,最直接的思路是,把个性化内容绘制在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

转换用的canvas容器,注意,该容器可以提前写入dom,也可以像上述代码所示,动态创建。两种调用方法并无区别,如果动态创建,不挂进dom树,则该容器全程是不可见的,所以对于单张一次性的图片生成,更推荐这种方式。

width、height

从待转换的源dom取得,如果源dom本身高度也处在动态变化中,请在源dom被正确绘制之后,再取宽高。

scale

一个影响表现的关键参数。如果不设置,在移动端会生成一张非常模糊的图片! 这也是使用html2canvas最常见的问题,这是由canvas本身的绘制原理导致的。因为移动端设备屏幕尺寸非常多,碎片化严重,所以我们常常使用rem等技术,在移动端使用比屏幕分辨率更大的素材图片,但canvas的绘制默认是按照屏幕分辨率来进行的,如果我们不对它做手工放大,素材图片就会被压缩。 scale参数就是用来做放大的,推荐设置为2,此时生成的分享图是屏幕绘制区域的两倍,如果对品质要求较高,需要适配三倍屏的情况,也可以动态切换为3。

background

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标签区域,就可以唤起端上的保存/分享功能,完成后续链路。

三、常见“谣言”

网上繁杂的祖传代码里,有一些常见的误区,先总结如下

1、能转成图片的必须是web上的可见区域?

No。

其实这个限制并不在于“可见”,而是在于dom引擎“渲染”,即使渲染后的结果通过某种方式被隐藏起来,一样可以画进canvas。比如下面的例子:

外层容器高度比内层更小,且overflow属性为hidden,此时,就可以转换一个比视觉区域更高的图。

这里笔者也提供了一个例子:

https://wendychengc.github.io/html2canvas-demo/demo/partial.html

由于没有美术大佬的协助,例子的效果比较简陋,但在实际应用中,这是一个很灵活的特性,比如我们可以在dom中把“长按二维码扫描”等等引导语隐藏起来,在用户保存或分享时,才在图片中展示。或者由web侧生成超过一屏的长图,分享到朋友圈等等。

请抱紧美术大佬的大腿,灵活使用。

2、只能使用img标签,不能使用background?

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越来越少。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我和我大前端的故事

2018 我所了解的 Vue 知识大全(一)

Vue ,React ,Angular 三大主流框架,最后我选择学习 Vue ,接触过 React ,自己感觉学习曲线有些陡峭,进而我选择了学习 Vue ,他的...

1033
来自专栏思衍 Jax 专栏

企鹅辅导课程详情页毫秒开的秘密 - PWA 直出

随着近几年的前端技术的高速发展,越来越多的团队使用 React、Vue 等 SPA 框架作为其主要的技术栈。以 React 应用为例,从性能角度,其最重要的指标...

99511
来自专栏我有一个梦想

UE4新手编程之创建C++项目

 虚幻4中常用的按键和快捷键 虚幻4中有一些按键和快捷键很常用,牢记它们并运动到实际的项目开发中,将会大大地提高你的工作效率和使得工作更简便快捷。下面将列举它们...

2666
来自专栏Material Design组件

Human Interface Guidelines — Widgets

1203
来自专栏程序员互动联盟

你所不知道的html5与html中的那些事(三)

文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么...

3046
来自专栏理论坞

UI(用户界面)设计规则和规范

界面是软件与用户交互的最直接的层,界面的好坏决定用户对软件的第一印象。而且设计良好的界面能够引导用户自己完成相应的操作,起到向导的作用。同时界面如同人的面孔,具...

2283
来自专栏王大锤

iOS各种调试技巧豪华套餐

4239
来自专栏大数据钻研

学习HTML的笔记

不知不觉我已经踏上了这条程序猿的不归路,从最基础的HTML标签开始学习,道阻且艰。谨用简书来记录这些 即使敌众我寡,末将亦能万军丛中取敌将首级! 框架 公司开发...

3169
来自专栏编程

5个最好的开源Javascript图表库

在这篇文章中,我向大家介绍前5名最好的开源JavaScript图表库。每个站点的仪表板都是不完整的,因为他们缺少图表,所以为我们的站点找到正确的图表库是非常重要...

4568
来自专栏iOS技术

iOS图片浏览器(功能强大/性能优越)

支持 cocopods,功能完善,性能不错,代码质量尚可,喜欢的朋友可以给个小星星?。

5687

扫码关注云+社区

领取腾讯云代金券