211

Without any extension library, is it possible to have multiple layers in the same canvas element?

So if I do a clearRect on the top layer, it will not erase the bottom one?

Thanks.

Gregoire
  • 24,219
  • 6
  • 46
  • 73
  • 5
    check this out.. http://html5.litten.com/using-multiple-html5-canvases-as-layers/ this will help you to resolve your issue in proper way – Dakshika Feb 23 '14 at 05:13
  • you may want to take a look at http://radikalfx.com/2009/10/16/canvas-collage/. he uses a sort of "layers" technique. – Matthew Jul 25 '10 at 11:23
  • @Dakshika Thank you for that link, it explained a problem I had using the canvas a few years ago that a library took care of for me. – Fering Apr 16 '19 at 18:57

9 Answers9

304

No, however, you could layer multiple <canvas> elements on top of each other and accomplish something similar.

<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>

Draw your first layer on the layer1 canvas, and the second layer on the layer2 canvas. Then when you clearRect on the top layer, whatever's on the lower canvas will show through.

jimr
  • 11,080
  • 1
  • 33
  • 32
  • is there a way of hiding/unhiding a layer.. such that i can hide layer1 and show layer2 and do vice-versa when required..?? – Zaraki Jun 12 '12 at 13:56
  • 6
    You could hide it with CSS - i.e `display: none;`. Or just clear the canvas, if it's not super expensive to redraw it again when the layer should be shown. – jimr Jun 12 '12 at 14:08
  • The values assigned to 'left' and 'top' need to be '0px', not '0'. – Bryan Green Dec 24 '15 at 16:58
  • 10
    @BryanGreen Not true. "However, for zero lengths the unit identifier is optional (i.e. can be syntactically represented as the 0)." https://www.w3.org/TR/css3-values/#lengths – xehpuk Feb 18 '16 at 23:00
  • Can I control the composition type for multiple canvas? – Ziyuan Nov 29 '19 at 21:26
  • using multiple canvas is [one of the performance advises](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#use_multiple_layered_canvases_for_complex_scenes) by MDN. – Leo Y Jan 17 '21 at 15:28
62

Related to this:

If you have something on your canvas and you want to draw something at the back of it - you can do it by changing the context.globalCompositeOperation setting to 'destination-over' - and then return it to 'source-over' when you're done.

   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" />
jmoreno
  • 12,752
  • 4
  • 60
  • 91
Richard
  • 4,809
  • 3
  • 27
  • 46
  • 2
    yes, that is fine but in case of erasing, as asked in question. this will erase both layers parallelly. which is again not correct. – Pardeep Jain Aug 23 '19 at 09:05
  • So is this essentially just a way to only draw on transparent/empty parts of the canvas? – dwb Jan 09 '21 at 16:12
  • 2
    **This is the incredible and amazing answer.** Thank you so much @Richard !!!!! – Fattie Feb 05 '21 at 16:08
  • Regarding this comment: >>> So is this essentially just a way to only draw on transparent/empty parts of the canvas? <<< It does do that but it may also affect areas of the canvas which are only semi-transparent. – Richard Feb 21 '21 at 14:58
  • This is the way to go for most cases, but in my case I needed to cut some shapes out from the top 2 "layer"s using destination-out and I couldn't make it work using globalCompositeOperation, hence went back to multiple canvases approach. – yenren Apr 04 '22 at 06:42
  • This is a most useful answer also for drawing something *on top* of other things, such as making sure that labels in a pie chart always show completely even if they overlap the next piece of the pie. Just push the circle segments into the back and you're done. Voted up. – MDickten Jun 29 '23 at 09:54
45

You can create multiple canvas elements without appending them into document. These will be your layers:

Then do whatever you want with them and at the end just render their content in proper order at destination canvas using drawImage on context.

Example:

/* 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);

And here is some codepen: https://codepen.io/anon/pen/mQWMMW

juszczak
  • 596
  • 4
  • 8
  • 5
    @SCLeo you said "Performance killer. About ~10 times slower" is completely wrong. Depending on use cases using a single DOM canvas and rendering offscreen canvases to that is quicker than stacking canvas in DOM. The common mistake is benchmarking rendering calls, canvas draw calls can be timed, DOM rendering is outside Javascripts context and can not be timed. The result is that the be DOM stacked canvas do not get the compositing render (done by DOM) included in the benchmark.. – Blindman67 Apr 18 '17 at 20:08
  • @Blindman67 I know what you mean. Just checkout this benchmark: https://jsfiddle.net/9a9L8k7k/1/. In case you misunderstand, there are three canvases, canvas 1 (ctx1) is a real canvas. Canvas 2 (ctx2) and canvas 3 (ctx) are off screen. The image has been previously rendered onto ctx3. In test 1 of this benchmark, I directly render ctx3 onto ctx1. In test 2, I render ctx3 onto ctx2 and then ctx2 to ctx1. Test 2 is 30 times slower than test 1 on my computer. That is why I say using a intermediate canvas is much slower. – SCLeo Apr 19 '17 at 04:24
  • @Blindman67 The trick of off-screen canvas only works when the off-screen canvas is static. Using dynamic canvases will greatly damage the performance. (Again, what I am trying to say is that dynamic off-screen canvas is extremely slow so presumably this technique (mock layers by multiple off-screen canvas) will not be desirable) – SCLeo Apr 19 '17 at 04:30
  • @Blindman67 IMPORTANT: The benchmark's address is [https://jsfiddle.net/9a9L8k7k/3](https://jsfiddle.net/9a9L8k7k/3) , I forget to save after editing and Stack Overflow does not let me to change the previous comment anymore... – SCLeo Apr 19 '17 at 04:37
  • @SCLeo Your tests are flawed. First `clear(ctx2); ctx2.draw(ctx3); clear(ctx); ctx.draw(ctx2)` should be `clear(ctx); ctx.draw(ctx3); ctx.draw(ctx2)` with result on my machine (timeline running) `test1 x 38,607 ops test2 x 24,853` as expected (note test2 is ~1/2 of test1). The second and most important flaw is that the tests are back to back as blocking JS in approx 50ms blocks..Look at the timeline after every test1 block there is a ~1.7ms Paint and 0.25ms composite (GPU green) which are not present after test2. The DOM does the draw as well but slower and your benchmark is not recording it. – Blindman67 Apr 19 '17 at 05:47
  • @SCLeo Further you must test the full cycle of render and presentation. A draw call is ~0.12ms. the DOM does 3 layers in 0.25ms (2 draw draws) Adding to a canvas layer above content forces the DOM to do the compositing, The single layered canvas can avoid this. The display is not magic and pixels must be composited and both the DOM and 2D API draw pixels at the same speed. Off screen canvases let you control when composting happens. Yes the benchmark favors stacked canvases because you are skipping presenting the content for display. – Blindman67 Apr 19 '17 at 06:11
  • @Blindman67 No, I am NOT testing layers here. I am just trying to show that render something to a off-screen canvas first and then to the actual canvas is slow. As you said, it is very hard to test actual layering because some of the work isn't done by JavaScript. Again, please read my first comment again. I am NOT testing actual layers. Or you can think this way: ctx1 is the actual canvas. ctx2 is the "layer" you are trying to create. ctx3 is a image that you want it to be on the canvas. By introducing a layer system (ctx2), the performance is dropped to 1/30 of original. – SCLeo Apr 19 '17 at 08:16
  • @SCLeo You have a problem with low GPU RAM, its not the offscreen canvas that to giving you the bad performance. Try adding the second canvas to the DOM, my guess is that rendering to both onscreen canvases gives you the same 3000% performance drop.I have been writing JS canvas code for a long time. Pixel rendering rates are the same no matter where the canvas is. GPU RAM < bus speeds > CPU RAM on the other hand will cause performance drops as you describe. With 2D canvas you can play full 1080p video with a dozen layers of 2D comp and not go under 60FPS – Blindman67 Apr 19 '17 at 10:02
  • 5
    @Blindman67 I am sorry, it is my mistake. I tested and find that using multiple off screen canvas is runs very smooth. I am still not sure why that benchmark shows that using off screen canvas is that slow. – SCLeo Apr 27 '17 at 10:20
  • Without appending them, how will they render on screen. I created multiple canvas elements using JS, but I am unable to display them on screen unless I append it to DOM? Am I missing something? – saibbyweb Nov 12 '18 at 08:59
  • @saibbyweb just use `drawImage` method on `CanvasRenderingContext2D` of your single `canvas` in the DOM – juszczak Nov 13 '18 at 14:23
  • @juszczak thanks for replying. Can you please help with this quick code pen (https://codepen.io/saibbyweb/pen/jQVEVR), I am still unable to render it without adding it to the dom. – saibbyweb Nov 13 '18 at 14:50
  • @juszczak you are a life saver! thanks a ton. followed you on git! – saibbyweb Nov 14 '18 at 12:00
11

I was having this same problem too, I while multiple canvas elements with position:absolute does the job, if you want to save the output into an image, that's not going to work.

So I went ahead and did a simple layering "system" to code as if each layer had its own code, but it all gets rendered into the same element.

https://github.com/federicojacobi/layeredCanvas

I intend to add extra capabilities, but for now it will do.

You can do multiple functions and call them in order to "fake" layers.

Federico Jacobi
  • 119
  • 1
  • 2
3

You might also checkout http://www.concretejs.com which is a modern, lightweight, Html5 canvas framework that enables hit detection, layering, and lots of other peripheral things. You can do things like this:

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();
Eric Rowell
  • 5,191
  • 23
  • 22
3

but layer 02, will cover all drawings in layer 01. I used this to show drawing in both layers. use (background-color: transparent;) in style.

    <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>
aymhenry
  • 81
  • 4
1

Based on the idea stacking multiple <canvas> elements, I made a demo Multiple Canvas Layers.

It contains 3 stacked layers: background (bg), intermediate (grid), foreground (fg).

To blend layers when saving/exporting, just create an OffscreenCanvas and invoke drawImage() to draw related <canvas> elements in order.

fuweichin
  • 1,398
  • 13
  • 14
0

I understand that the Q does not want to use a library, but I will offer this for others coming from Google searches. @EricRowell mentioned a good plugin, but, there is also another plugin you can try, html2canvas.

In our case we are using layered transparent PNG's with z-index as a "product builder" widget. Html2canvas worked brilliantly to boil the stack down without pushing images, nor using complexities, workarounds, and the "non-responsive" canvas itself. We were not able to do this smoothly/sane with the vanilla canvas+JS.

First use z-index on absolute divs to generate layered content within a relative positioned wrapper. Then pipe the wrapper through html2canvas to get a rendered canvas, which you may leave as-is, or output as an image so that a client may save it.

dhaupin
  • 1,613
  • 2
  • 21
  • 24
  • If you have heavier images, this will take some time to convert HTML to canvas, we had to move away from this just because the rendering took long time. – Vilius Sep 27 '17 at 15:36
  • @Vilius yeah good call on the heavy/large images. We tried to stick to 300K or less images with no more than 4 layers, else resource stricken clients would feel the burn when downloading final composted image. Curious, what did you move to that reduced time? – dhaupin Oct 07 '17 at 22:10
  • Well, we made a big mistake by using html elements to draw something in the first place. Because our api returned x,y,width and height, we moved to jscanavs to draw the image instead of using html elements. Mind you we did have couple of issues with rotation (starting points were a bit awkward and unpredictable) and applying images to it using specific dimensions but all was solved eventually. We also found that our image processing application was draining a lot of resource, so we moved away from that too. – Vilius Oct 11 '17 at 11:21
0

Yes, it is possible.

Since the Canvas element has an integrated structure like in Paint, the first thing you need to do is to store all the drawings in a global variable. Then, for each change you make in this variable, you will automatically redraw the changes on the Canvas. The subsequent steps include displaying the data from the global variable on the screen, presenting them like layers, and for the left-right movement using the mouse on the Canvas, you will compare the height values in your global variable by utilizing the properties of the Canvas for mouseover and click, and draw an area accordingly.

ksomaz
  • 11
  • 3