←首页

最近做的需求,H5 页面需要实现 “长按保存到本地”,而图片的内容是不固定的,有用户名字,匹配度,头像等等。尝试过几种实现方式,写篇文章记录下。

扫码看效果

使用开源库 html2canvas.js

看到需求第一眼就想到这个库 html2canvas (之前在掘金上看到有人分享过)
开源框架,以后还能复用!!!
那一瞬间,感觉自己无所不能,信心满满地跟产品保证…

事实证明,还是太年轻了。

太年轻了

github 上 600 多个 issues 没有解决,官方也不推荐在正式环境上使用。

The script is still in a very experimental state, so I don’t recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made.

实际体验的时候也发现很多坑,比如 DOM 元素不是在视口左上角,或者页面滚动页面会导致截图错误。
折腾了一下还是决定放弃了。

svg to blob

搜索一番,在 MDN 上找到这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
'<foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
'<em>I</em> like ' +
'<span style="color:white; text-shadow:0 0 2px blue;">' +
'cheese</span>' +
'</div>' +
'</foreignObject>' +
'</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml'});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;

实现原理就是把 dom 所有样式内联之后,以 svg foreignObject 为载体生成 Blob 对象,
最终生成的 url 是这样的: blob:http://****。所以只要样式内联就可以了?

这时候又搜到另一个库 dom-to-image , 内联都不用自己写。然而,又一次…

Blob 形式生成的图片,在微信里面长按保存失败!!! 失败!!! GG

后端生成

后端渲染需要前端提供一个内容模板,后端往页面里面塞入内容之后生成图片,并截图返回。这种方式并没有减少多少工作量,因为每次改一次样式都需要更新模板。并且后端需要做好缓存和扩容,流量大的时候可能机器就挂掉了。

以前做过一个相关 pc 端页面不炸不知道
我只负责 pc 端,请用电脑打开

不炸不知道

考虑到种种可能出现的问题,这次也没有采用这种方式。

手动绘制

一把梭
前面几种方式都不能满足需求,又到了拼体力的时候。
手动绘制最大的问题是,所有元素都脱离“文档流”,没有 CSS 样式。那么,手动定位,文字换行,图片圆角,都是工作量!

最终我选择 createjs EASELJS 来辅助绘制,createjs 提供的类 Text,Bitmap,Container, Shape 可以方便绘制文字,图片,分组,图形,不至于一切都回到刀耕火种的农耕时代。

说了这么多…
最终做出来的样子…
是这样的:

扫码看效果

总结

  • 绘制图片需要等图片下载完毕,通常做法是写一个图片预加载器,把所有依赖的图片预先加载一次,加载完成之后再进行绘制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class ImagePreloader {
    loadManifest (manifest) {
    var promises = []
    for (var i = 0; i < manifest.length; i++) {
    promises.push(this.loadImage(manifest[i]))
    }
    return Promise.all(promises)
    }
    loadImage (src) {
    let image = new Image()
    image.crossOrigin = 'Anonymous'
    return new Promise((resolve, reject) => {
    image.onload = () => {
    resolve(image)
    }
    image.onerror = () => {
    reject()
    }
    image.src = src
    })
    }
    }
  • 其他域下的图片可能会存在跨域问题,比如直接使用微信接口返回的头像,绘制到画布上取不出来。解决方案就是后端转存头像到自己域下或自己的 cdn ,开启响应头 Access-Control-Allow-Origin: * 即可。

  • 低端机器上偶现 crash,问题未知。

如需转载,请注明出处: http://w3ctrain.com/2017/07/24/gen-image-fe/