49

Is it possible to set the z-index of a drawn object in HTML5 canvas?

I am trying to get it so one object can be infront of a the "player" and another object is behind the "player"

j0k
  • 22,600
  • 28
  • 79
  • 90
Eli Stone
  • 1,515
  • 4
  • 33
  • 57
  • That is absolutely possible, only thing is you will have to manage array of objects to be drawn yourself. – Software Enthusiastic Feb 06 '12 at 19:19
  • There are a couple solutions: using multiple canvas elements or choosing the order you draw objects on the canvas properly. See the following questions: * [implementing-layers-in-html5-canvas](http://stackoverflow.com/questions/4422907/implementing-layers-in-html5-canvas) * [html5-canvas-element-multiple-layers](http://stackoverflow.com/questions/3008635/html5-canvas-element-multiple-layers) – styfle Feb 06 '12 at 19:15

5 Answers5

75

Yes..kind of yes. You can use globalCompositeOperation to "draw behind" existing pixels.

ctx.globalCompositeOperation='destination-over';

Here's an example and a Demo:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

var cx=100;

drawCircle()
cx+=20;

ctx.globalCompositeOperation='destination-over';

$("#test").click(function(){
  drawCircle();
  cx+=20;
});

function drawCircle(){
  ctx.beginPath();
  ctx.arc(cx,150,20,0,Math.PI*2);
  ctx.closePath();
  ctx.fillStyle=randomColor();
  ctx.fill();
}

function randomColor(){ 
  return('#'+Math.floor(Math.random()*16777215).toString(16));
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="test">Draw new circle behind.</button><br>
<canvas id="canvas" width=300 height=300></canvas>
Carson
  • 6,105
  • 2
  • 37
  • 45
markE
  • 102,905
  • 11
  • 164
  • 176
  • why do you say 'kind of'? for me this looks like the only real solution here. – lordvlad Nov 08 '14 at 23:33
  • 6
    I say "kind of" because compositing is not as flexible as refactoring the drawings to appear properly z-layered in the first place. Compositing involves only 2 "layers". One layer is the existing pixels on the canvas and the second layer is newly drawn pixels. Refactoring to draw properly layered drawings yields as many layers as necessary. – markE Nov 09 '14 at 03:09
  • 1
    stackoverflow is great! – Li3ro Feb 13 '17 at 08:56
  • 2
    Loving the use of ```toString(16)``` - you've taught me a new trick there :) – Mike Apr 10 '17 at 14:01
  • @Mike Im really curious what is `toString(16)` doing ? – conor909 May 31 '18 at 08:50
  • 1
    @conor909 its converting a number to hexadecimal format then returning as string, so basically it allows you to easily transform numeric rgb values to their hex string equivalents. try running the randomColor() function shown above in your console to see it in action – Mike May 31 '18 at 10:51
  • Thanks for the shared resource! It has been useful to find the best solution for me, `ctx.globalCompositeOperation = "overlay";` to have the canvas element always on top of the others during the drag-n-drop event. Cheers – Riccardo Volpe Jul 18 '21 at 11:00
7

A solution that I've found works for me (and gets rid of flickering, hurrah!) is to use two canvases. (or more)

I will assume you are making a game of some kind (since you mention a player) and use that as an example.

You can take a page from the way windows works and put a canvas first as a background with another canvas over it as your player canvas. You can mark the player canvas as 'dirty' or changed whenever it has been altered and only redraw the changed layer when needed. This means that you only update the second canvas when your player moves or takes an action.

This method can be taken even farther and you can add a third canvas for a HUD with gameplay stats on it that is only changed when the player's stats change.

The html might look something like this:

<canvas id="background-canvas" height="500" width="1000" style="border:1px solid #000000;"></canvas>
<canvas id="player-canvas" height="500" width="1000" style="border:1px solid #000000;"></canvas>
<canvas id="hud-canvas" height="500" width="1000" style="border:1px solid #000000;"></canvas>
Community
  • 1
  • 1
nicholeous
  • 677
  • 1
  • 8
  • 13
6

Or you could simply use an array containing your objects to be drawn then sort this array using the zIndex property of each child. Then you just iterate over that array and draw childrens.

var canvas = document.querySelector('#game-canvas');
var ctx = canvas.getContext('2d');

// Define our shape "class".
var Shape = function (x, y, z, width, height) {
  this.x = x;
  this.y = y;
  this.zIndex = z;
  this.width = width;
  this.height = height;
};
// Define the shape draw function.
Shape.prototype.draw = function () {
  ctx.fillStyle = 'lime';
  ctx.fillRect(this.x, this.y, this.width, this.height);
};

// let's store the objects to be drawn.
var objects = [
  new Shape(10, 10, 0, 30, 30), // should be drawn first.
  new Shape(50, 10, 2, 30, 30), // should be drawn third.
  new Shape(90, 10, 1, 30, 30)  // should be drawn second.
];

// For performance reasons, we will first map to a temp array, sort and map the temp array to the objects array.
var map = objects.map(function (el, index) {
  return { index : index, value : el.zIndex };
});

// Now we need to sort the array by z index.
map.sort(function (a, b) {
  return a.value - b.value;
});

// We finaly rebuilt our sorted objects array.
var objectsSorted = map.map(function (el) {
  return objects[el.index];
});

// Now that objects are sorted, we can iterate to draw them.
for (var i = 0; i < objectsSorted.length; i++) {
  objectsSorted[i].draw();
}

// Enjoy !

Note that I didn't tested that code and wrote it on my cellphone, so there might be typos, but that should permit to understand the principle, i hope.

SkyzohKey
  • 755
  • 7
  • 16
  • I think `map.sort(function (a, b) { return a - b; });` should be `map.sort(function (a, b) { return a.value - b.value; });`, I don't think you can just minus two objects. – dud3 Jun 14 '17 at 09:06
  • So this implies the objects need to drawn sequentially? How can we improve the performance in this case? – jche May 08 '22 at 07:20
6

Just draw the things behind it first, then the thing, then the other objects.

To do hit testing you may need to iterate backwards over your display list, testing each object. This will work if you know the object boundaries really well.

Or you may want to try some standard graphics tricks like drawing the same objects to another in-memory canvas with unique colours for every object drawn: to hit test this just check the pixel colour on the in-memory canvas. Neat ;-)

Joe Parry
  • 241
  • 2
  • 7
2

Sorry, but nope, the canvas element will have its z-index and anything drawn on it will be on that layer.

If you are referring to different things on the canvas then yes, anything that is drawn is drawn on top of whatever was there before.

Andrew
  • 13,757
  • 13
  • 66
  • 84