dom还能转成图片?

前言

最近工作中需要将网页上的内容转换为图片,供用户保存再发到朋友圈里,也就是说对一部分dom做“截图”处理,然后生成img文件,下载到本地。

方法

在网上收集了几种方案,这里我们列出来:

  1. 如果是新的内容,那么用canvas来绘制,如果是以前就有的 DOM 内容,那么将 DOM 改写为 canvas ,然后利用 canvas 的 toDataURL 方法实现将 DOM 输出为包含图片展示的 data URI,下载到本地。

  2. 使用 html2canvas.js 将 DOM 转换为canvas,然后利用 canvas 的 toDataURL 方法实现将 DOM 输出为包含图片展示的 data URI,下载到本地。

  3. 后端服务器生成,如果后端小哥是你小弟的话…

实践

我们要实现一个简单的 demo ,这个 demo 有一张图片和几段文字,我们依次按照上面两种方案来实现,通过对比其实现过程和效果来总结出最佳实践

方案一:手撸canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class App extends React.Component{
componentDidMount() {
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = "http://osutuwgm1.bkt.clouddn.com/testcat.png";
img.onload = function(){
ctx.drawImage(img, 70, 10, 260, 200);
ctx.font = "30px Courier New";
ctx.fillStyle = "skyblue";
ctx.fillText("Canvas中文测试", 90, 270);
};
}
handleDownLoad = () => {
const canvas = document.getElementById("myCanvas");
// 转换为base64文件流
const imgUri = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
// 下载图片
window.location.href= imgUri;
}
render() {
return(
<div>
<canvas id="myCanvas" width="400px" height="300px" style={{border: '1px solid red'}} />
<button onClick={this.handleDownLoad}>Click</button>
</div>
)
}
}

通过上面一大串代码,我们好不容易得到下面的canvas图:

image

当一切就绪后,点download,发现页面直接报错了:

image

这是为什么呢?

canvas的图片必须要同源才能被我们转换为baseData文件,难道我们需要将这些图片都放到同一个服务器下吗?显然不合理,我们找到只要加这么一行代码,就能实现跨域加载图片:

1
2
3
4
5
6
...
const img = new Image();
img.src = "http://osutuwgm1.bkt.clouddn.com/testcat.png";
img.setAttribute('crossOrigin','anonymous'); // 跨域访问
img.onload = function(){
...

现在点击download后,便能下载到canvas中的图像。

我们思考,假设我们要实现的样式很复杂,难道也要用canvas来手撸出来吗?显然会很复杂,并且对于样式的维护也会变得很庞大,有没有更好的方式呢?

html2canvas 自动化

html2canvas 中的2是啥意思呢? 也就是谐音 to 的意思。。。(好无聊啊hhhhh),字面上的意思就是将html自动转换为canvas,我们又知道canvas能够转换为图片格式的文件,那么我们岂不是只要写html就能随意的生成我们想要的样式,再也不要写那么复杂的canvas了,岂不美哉,show me code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class App extends React.Component {
handleClick = () => {
const dom = this.refs.xixix;
const opts = { useCORS: true }; // 解决跨域
html2canvas(dom, opts).then(function (canvas) {
var imgUri = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); // 获取生成的图片的url
window.location.href= imgUri; // 下载图片
});
}
render() {
return (
<div>
<div ref="xixix" className="container">
<img src="http://osutuwgm1.bkt.clouddn.com/testcat.png" alt="" />
<p>HTML 中文测试</p>
</div>
<button onClick={this.handleClick}>click</button>
</div>
)
}
}

如图:

image

当我们点击Click后,下载的图片和上面canvas的一样,但是相比起来,用 html CSS 来绘我们的样式,会更加简单高效些。也方便后期的更新与维护。

当然,html2canvas 并不是完美的,他有一下限制:

  • 不支持iframe
  • 不支持跨域图片
  • 不能在浏览器插件中使用
  • 部分浏览器上不支持SVG图片
  • 不支持Flash
  • 不支持古代浏览器和IE
  • 部分新的CSS未支持,如 box-shadow

其实从最后一点,我们可以看出,这并不是一个非常完美的工具,因为要去去支持新的CSS,还需要等待作者来更新维护,假设一个CSS对你的项目非常重要,但是确一直未维护,那么就非常尴尬了,这就需要你手动来写canvas了。

彩蛋

两种方法都使用过了,这里我再给大家一个非常实用的函数,将文件重命名后再下载:

1
2
3
4
5
6
7
8
const saveFile = function (data, filename) {
const save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
save_link.href = data;
save_link.download = filename;
const event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
save_link.dispatchEvent(event);
};

Last

我们使用两种方式尝试去解决我们的问题,但最后都发现没有完美的解决方案,canvas可以实现任何效果,只不过会很复杂不易维护,html2canvas 非常便捷,但是不支持所有CSS,针对比较简单的样式还是游刃有余的。具体使用哪种,就需要看实际情况~

It is not easy to meet each other in such a big world. :)