HTML5 - 画布元素 - 多层级

211

在不使用扩展库的情况下,是否可以在同一画布元素中拥有多个图层?

因此,如果我在顶层执行clearRect操作,它将不会擦除底部图层吗?

谢谢。


你可能想看一下这个网址:http://radikalfx.com/2009/10/16/canvas-collage/。他使用了一种“图层”技术。 - Matthew
5
看这个:http://html5.litten.com/using-multiple-html5-canvases-as-layers/ 这可以帮助你以适当的方式解决问题。 - Dakshika
@Dakshika 谢谢你提供的链接,它解释了我几年前使用画布时遇到的一个问题,当时一个库为我解决了这个问题。 - Fering
9个回答

306
不行,但是你可以将多个<canvas>元素叠加在一起,从而实现类似的效果。
<div style="position: relative;">
 <canvas id="layer1" width="100" height="100" 
   style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
 <canvas id="layer2" width="100" height="100" 
   style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</div>

layer1画布上绘制第一层,然后在layer2画布上绘制第二层。当你在顶层调用clearRect时,下层画布上的任何内容将会显示出来。


有没有一种方法可以隐藏/取消隐藏一个图层,这样我就可以在需要时隐藏图层1并显示图层2,反之亦然..?? - Zaraki
6
你可以通过CSS来隐藏它,即display:none;。或者仅清除画布,如果在应该显示该层时重新绘制它不会太耗费资源。 - jimr
“left”和“top”属性的值应该为“0px”,而不是“0”。 - Bryan Green
10
@BryanGreen 不正确。 "然而,对于长度为零的情况,单位标识符是可选的(即可以在语法上表示为<number> 0)"。https://www.w3.org/TR/css3-values/#lengths - xehpuk
我可以控制多个画布的构图类型吗? - Ziyuan
使用多个画布是MDN提供的性能建议之一 - Leo Y

62

与此相关:

如果您在画布上有绘制内容,并想在其后面绘制其他内容,可以通过将context.globalCompositeOperation设置为'destination-over'来实现-然后在完成后将其返回到'source-over'。

   var context = document.getElementById('cvs').getContext('2d');

    // Draw a red square
    context.fillStyle = 'red';
    context.fillRect(50,50,100,100);



    // Change the globalCompositeOperation to destination-over so that anything
    // that is drawn on to the canvas from this point on is drawn at the back
    // of what's already on the canvas
    context.globalCompositeOperation = 'destination-over';



    // Draw a big yellow rectangle
    context.fillStyle = 'yellow';
    context.fillRect(0,0,600,250);


    // Now return the globalCompositeOperation to source-over and draw a
    // blue rectangle
    context.globalCompositeOperation = 'source-over';

    // Draw a blue rectangle
    context.fillStyle = 'blue';
    context.fillRect(75,75,100,100);
<canvas id="cvs" />


2
是的,这没问题,但是如果按照问题所要求的擦除,这将同时擦除两个图层。这又是不正确的。 - Pardeep Jain
这只是一种仅在画布的透明/空白部分绘制的方法吗? - dwb
2
这是惊人而令人难以置信的答案。非常感谢@Richard!!!!! - Fattie
关于这条评论:>>>那么这基本上只是一种仅在画布的透明/空白部分绘制的方法吗?<<< 它确实可以做到这一点,但也可能会影响画布上仅半透明的区域。 - Richard
这是大多数情况下的最佳选择,但在我的情况下,我需要使用destination-out从顶部2个“层”中剪切一些形状,而我无法使用globalCompositeOperation使其正常工作,因此回到了多画布方法。 - yenren
这是一个非常有用的答案,也适用于在其他物体上绘制东西,比如确保饼图中的标签始终完整显示,即使它们与饼图的下一个部分重叠。只需将圆形片段推到后面,问题就解决了。投票支持。 - MDickten

46
您可以创建多个canvas元素,而无需将它们附加到文档中。这些将成为您的
然后,您可以按照需要对它们进行操作,并最终使用目标canvas上的context中的drawImage以适当的顺序呈现其内容。
例如:
/* using canvas from DOM */
var domCanvas = document.getElementById('some-canvas');
var domContext = domCanvas.getContext('2d');
domContext.fillRect(50,50,150,50);

/* virtual canvase 1 - not appended to the DOM */
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(50,50,150,150);

/* virtual canvase 2 - not appended to the DOM */    
var canvas2 = document.createElement('canvas')
var ctx2 = canvas2.getContext('2d');
ctx2.fillStyle = 'yellow';
ctx2.fillRect(50,50,100,50)

/* render virtual canvases on DOM canvas */
domContext.drawImage(canvas, 0, 0, 200, 200);
domContext.drawImage(canvas2, 0, 0, 200, 200);

这里有一些 CodePen 代码:https://codepen.io/anon/pen/mQWMMW


5
@SCLeo说“性能杀手,慢了大约10倍”是完全错误的。根据用例,使用单个DOM画布并渲染到离屏画布上比在DOM中堆叠画布更快。常见的错误是对渲染调用进行基准测试,Canvas绘制调用可以计时,DOM渲染在Javascript上下文之外,无法计时。结果是在DOM中堆叠的Canvas不会得到组合渲染(由DOM执行)包括在基准测试中。 - Blindman67
@Blindman67 重要提示:基准测试的地址是https://jsfiddle.net/9a9L8k7k/3,我在编辑后忘记保存了,而且 Stack Overflow 不再允许我更改之前的评论了... - SCLeo
@SCLeo 你的测试有缺陷。首先 clear(ctx2); ctx2.draw(ctx3); clear(ctx); ctx.draw(ctx2) 应该改为 clear(ctx); ctx.draw(ctx3); ctx.draw(ctx2),在我的机器上运行时(时间轴正在运行),结果为 test1 x 38,607 ops test2 x 24,853,如预期(请注意,test2 的结果约为 test1 的一半)。第二个也是最重要的缺陷是测试是连续的,并将 JavaScript 阻塞在大约 50 毫秒左右的块中。在每个 test1 块之后查看时间轴,会发现大约有 1.7 毫秒的画面更新和 0.25 毫秒的合成(GPU 绿色),但在 test2 之后不存在。DOM 也会进行绘制,但速度较慢,而你的基准测试没有记录这一点。 - Blindman67
@SCLeo 你的GPU内存不足,不是离屏画布造成的性能问题。尝试将第二个画布添加到DOM中,我猜想渲染到两个屏幕画布会导致相同的3000%性能下降。我已经写了很长时间的JS画布代码。像素渲染速率不管画布在哪里都是一样的。但是GPU内存<总线速度>CPU内存则会导致性能下降,就像你所描述的那样。使用2D画布,您可以播放完整的1080p视频,并具有十几个2D组合层,而不会低于60FPS。 - Blindman67
5
@Blindman67 对不起,这是我的错误。我进行了测试,并发现使用多个离屏画布非常流畅。我仍然不确定为什么那个基准测试显示使用离屏画布如此缓慢。 - SCLeo
显示剩余8条评论

11

我也遇到了同样的问题,虽然使用多个带有position:absolute属性的画布元素可以完成任务,但是如果你想将输出保存为图像,那是行不通的。

所以我采用了一个简单的分层“系统”来编码,就好像每个层都有自己的代码一样,但是所有内容都会呈现在同一个元素中。

https://github.com/federicojacobi/layeredCanvas

我打算增加额外的功能,但目前已经足够使用了。

您可以执行多个函数并按顺序调用它们来“模拟”图层。


这个完美无缺。 - Nadir

3

你也可以查看 http://www.concretejs.com,它是一个现代化、轻量级的Html5画布框架,可以实现碰撞检测、分层和许多其他外围功能。您可以像这样做:

var wrapper = new Concrete.Wrapper({
  width: 500,
  height: 300,
  container: el
});

var layer1 = new Concrete.Layer();
var layer2 = new Concrete.Layer();

wrapper.add(layer1).add(layer2);

// draw stuff
layer1.sceneCanvas.context.fillStyle = 'red';
layer1.sceneCanvas.context.fillRect(0, 0, 100, 100);

// reorder layers
layer1.moveUp();

// destroy a layer
layer1.destroy();

这些层将以何种方式出现在DOM中?每个层都可以通过CSS访问吗? - Garavani
OP: "不使用任何扩展库"。寻找纯HTML5解决方案。 - Dominic Cerisano

3
但第二层将覆盖第一层中的所有图纸。我使用这种方法来在两个图层中显示绘画。在样式中使用(background-color: transparent;)。

    <div style="position: relative;"> 
      <canvas id="lay01" width="500" height="500" style="position: absolute; left: 0; top: 0; z-index: 0; background-color: transparent;">
      </canvas> 
      <canvas id="lay02" width="500" height="500" style="position: absolute; left: 0; top: 0; z-index: 1; background-color: transparent;">
      </canvas>
</div>


1
基于叠加多个<canvas>元素的想法,我制作了一个演示多层画布
它包含3个叠加的图层:背景(bg)、中间层(grid)和前景(fg)。
要在保存/导出时混合图层,只需创建一个离屏画布,并调用drawImage()按顺序绘制相关的<canvas>元素。

0

我知道Q不想使用库,但是我会为那些通过谷歌搜索而来的人提供这个建议。@EricRowell提到了一个好的插件,但是还有另一个插件可以尝试,html2canvas

在我们的情况下,我们使用带有z-index的分层透明PNG作为“产品构建器”小部件。Html2canvas非常出色地将堆栈简化,而不会推动图像,也不会使用复杂性、解决方法和“非响应式”的画布本身。我们无法使用原始的canvas+JS平稳/理智地完成这项工作。

首先,在相对定位的包装器中使用绝对divs上的z-index生成分层内容。然后将包装器通过html2canvas传输以获取渲染的画布,您可以将其保留不变,或者将其输出为图像,以便客户端保存。


如果您有更重的图像,将HTML转换为画布会花费一些时间,我们不得不放弃这种方法,因为渲染需要很长时间。 - Vilius
@Vilius,你说得对,关于大型图片的问题需要注意。我们尽量使用300K或更小的图片,并且不超过4层,否则资源匮乏的客户在下载最终合成图像时会感到困难。好奇一下,你采取了什么措施来缩短时间? - dhaupin
我们犯了一个大错误,一开始使用HTML元素来绘制图像。因为我们的API返回x、y、宽度和高度,所以我们转而使用jscanavs来绘制图像,而不是使用HTML元素。请注意,我们在旋转方面遇到了一些问题(起始点有些尴尬和不可预测),并且使用特定尺寸应用图像也存在一些问题,但最终都得到了解决。我们还发现我们的图像处理应用程序消耗了很多资源,所以我们也放弃了它。 - Vilius
@Villus:有趣。你最后用了什么? - undefined

0
是的,这是可能的。
由于Canvas元素具有像绘图软件中一样的内置结构,首先要做的事情是将所有的绘图存储在一个全局变量中。然后,对于你在这个变量中所做的每一个改变,你将自动重新绘制Canvas上的改变。接下来的步骤包括将全局变量中的数据显示在屏幕上,以图层形式呈现它们,并且对于在Canvas上使用鼠标进行左右移动,你将通过利用Canvas的mouseover和click属性,通过比较全局变量中的高度值,并相应地绘制一个区域。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接