用Canvas实现图层

在做一些绘图类应用时,有时会遇到处理“图层”的问题,而HTML5原生的Canvas并没有“图层”的概念,所以需要一些特别的手段来实现多图层绘图。本文介绍一种多canvas重叠模拟图层的方法来实现多图层的各种操作。

绘图区构建

用一个父元素作为容器,把所有的canvas元素设置成一样的宽高并放在里面重叠。

1
2
3
4
5
6
7
<div class="container">
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500"></canvas>
<canvas width="500" height="500"></canvas>
</div>

具体方法是父元素设置为相对定位,所有canvas设置为绝对定位,并且设置lefttop0,使所有的canvas元素重叠

1
2
3
4
5
6
7
8
.container{
position: relative;
}
.container canvas{
position: absolute;
left: 0;
top: 0;
}

图层操作

一般还会有图层管理面板来进行图层的操作,需要保持canvas元素状态和图层管理面板同步,用原生js来操作DOM实现是极其繁琐的,推荐使用一种框架,比如VueReactAngular等来进行。由于不同框架实现图层操作方法不同,这里只给出原生js的实现思路。

新建图层

操作DOM,在父容器上新增canvas元素,并将其宽高设置为绘图区宽高。

删除图层

操作DOM,在父容器上删除图层对应的canvas元素。

切换当前图层

获取对应图层的context,赋值给当前context

改变图层顺序

操作DOM,把canvas元素的顺序随之改变。

变换图层

为了方便,使用原生canvas API里面的各种API即可:

  • 移动:translate(x, y)
  • 旋转:rotate(angle)
  • 缩放:scale(x, y)
  • 变形:transform(m11, m12, m21, m22, dx, dy)

详见:变形 Transformations - Web API 接口 | MDN

绘图区的保存与导出

导出图像

分别导出所有canvas元素的图像,并按照图层从底部到顶部的顺序,绘制到一个canvas元素中,再导出图像。

1
2
3
4
5
6
7
8
9
10
11
12
13
let canvases = document.getElementsByTagName('canvas'),
canvasOutput = document.createElement('canvas'),
ctxOutput = canvasOutput.getContext('2d');

//倒序遍历canvas元素,并将其按顺序绘制在同一个canvas元素上,合成为一个图层
for (let i = canvases.length - 1; i >= 0; i--) {
let image = new Image();
image.src = canvases[i].toDataURL();
ctxOutput.drawImage(image, 0, 0);
}

//导出合成图像的src(Base64编码),可通过toDataURL函数的参数指定导出格式
let srcOutput = canvasOutput.toDataURL();

保存绘图区状态

记录各图层图像、名称、顺序等信息,其中图像使用Base64编码成字符串,其它信息直接使用字符串,共同保存为一个json文件,进行压缩,读取时,只需解压后还原各图层即可。

这里简单列出保存文件的大致实现方法,没有进行压缩。

1
2
3
4
5
6
7
8
9
10
11
12
let doc = {
layers: []
};

//遍历canvas元素,保存其图像
canvases.forEach(function (canvas) {
doc.layers.push({
image: canvas.toDataURL()
});
});

download.href = "data:application/octet-stream;base64," + btoa(JSON.stringify(doc));

The End