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 条评论
登录 后参与评论

相关文章

来自专栏向治洪

ReactJs和React Native的那些事

介绍  1,React Js的目的 是为了使前端的V层更具组件化,能更好的复用,它能够使用简单的html标签创建更多的自定义组件标签,内部绑定事件,同时可以让你...

19710
来自专栏腾讯开源的专栏

【开源公告】通用 Web 组件化框架 Omi 正式开源

Omi 从 3.0 开始基于 preact 二次开发,完全可以共享 preact 和 react 的生态,也有了自己独特的风格和优势。

7.4K7
来自专栏分布式系统和大数据处理

Web标准中的常见问题

大概在2004年的时候,Web标准的概念藉由一本名为《网站重构》的书开始被国内人所了解。随后的几年中,其更少的代码量、更好的搜索引擎友好性、更好的浏览器兼容性使...

985
来自专栏无原型不设计

Balanced-工具类App原型分享

Balanced是一款记事类的工具App,这类App在设计的时候讲究简单易用,如果操作太复杂,就不能做到记录事件的及时性和快速性。在制作原型时,这次尝试了将A...

3175
来自专栏王大锤

iOS各种调试技巧豪华套餐

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

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

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

943
来自专栏河湾欢儿的专栏

html5标签

什么是html5? 仅仅是狭义的概念。h5草案前身叫做web application 由WHATWG组织编写,在2007年提交到了w3c,w3c起名叫做HTM...

2291
来自专栏思衍 Jax 专栏

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

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

91111
来自专栏开源项目

绝对想尝试的创意 Android 库,你关注了吗?| 码云周刊第 43 期

码云项目推荐 随着 Android 开发走向成熟,每天都会涌现出各种各样与 Android 相关的开发工具,但是我们每天使用的各类库总是不可或缺的。这里,小...

4169
来自专栏理论坞

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

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

1693

扫码关注云+社区