一, 应用场景
在 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.
官网 http://html2canvas.hertzen.com/
于是, 我们的解决路径变成了下图:
该项目文档较为简单, 且历史版本存在着各种问题. 比如部分 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 的时候, 用透明底色作为填充色, 这样就可以愉快地生成圆角, 半透明等图片了.
完整示例:
请使用 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, 此时, 就可以转换一个比视觉区域更高的图.
这里笔者也提供了一个例子:
由于没有美术大佬的协助, 例子的效果比较简陋, 但在实际应用中, 这是一个很灵活的特性, 比如我们可以在 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 三端的昵称和测试结果动态合成.
来源: https://www.qcloud.com/developer/article/1356175