HTML5画布设置z-index

51

在HTML5画布中,是否可以设置绘制对象的z-index?

我正在尝试让一个对象在“玩家”前面,另一个对象在“玩家”后面。


有几种解决方案:使用多个画布元素或正确选择在画布上绘制对象的顺序。请参阅以下问题:* 在HTML5画布中实现图层 * HTML5画布元素多图层 - styfle
这是完全可能的,唯一的问题是你必须自己管理要绘制的对象数组。 - Software Enthusiastic
5个回答

75

是的,有点是。您可以使用globalCompositeOperation在现有像素“后面绘制”。

ctx.globalCompositeOperation='destination-over';

这里有一个示例和演示:

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>


你为什么说“有点儿”?对我来说,这似乎是唯一的真正解决方案。 - lordvlad
6
我说“有点儿”是因为合成不像重新绘制图层那样灵活,以便于一开始就呈现出正确的分层效果。合成仅涉及两个“层”。一个层是画布上现有的像素,第二个层是新绘制的像素。重新绘制以呈现正确分层的绘画将产生所需数量的层。 - markE
1
stackoverflow很棒! - Li3ro
2
喜欢使用toString(16) - 你教会了我一个新技巧 :) - Mike
1
@conor909 这段代码将数字转换为十六进制格式,然后返回字符串,因此基本上它允许您轻松地将数字 RGB 值转换为其十六进制字符串等效项。尝试在控制台中运行上面显示的 randomColor() 函数以查看其效果。 - Mike
显示剩余2条评论

7

或者您可以使用包含要绘制对象的数组,然后使用每个子项的 zIndex 属性对该数组进行排序。然后您只需迭代该数组并绘制子项即可。

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 !

请注意,我没有测试过这段代码,是在手机上编写的,因此可能有错别字,但这应该可以让你理解原理,希望如此。

我认为 map.sort(function (a, b) { return a - b; }); 应该改为 map.sort(function (a, b) { return a.value - b.value; });,我认为你不能只是减去两个对象。 - dud3
那么这意味着对象需要按顺序绘制?在这种情况下,我们如何提高性能? - jche

7

首先,先画出它后面的物体,然后是这个物体,最后是其他物品。

要进行命中测试,您可能需要反向迭代您的显示列表,逐个测试每个对象。如果您非常了解对象边界,则可以使用此方法。

或者,您可能想尝试一些标准的图形技巧,比如将相同的对象绘制到另一个内存画布上,并为每个绘制的对象使用唯一的颜色:要进行命中测试,只需检查内存画布上的像素颜色。很不错吧 ;-)


7
我找到的解决方法(并且消除了闪烁,太好了!)是使用两个画布或更多。我假设你正在制作一款游戏(因为你提到了玩家),以此作为例子。你可以借鉴Windows的工作方式,先放置一个画布作为背景,再覆盖一个画布作为玩家画布。每当修改后,你可以将玩家画布标记为“脏”或已更改,并且仅在需要时重新绘制更改的层。这意味着只有在玩家移动或采取行动时才更新第二个画布。这种方法甚至可以进一步运用,例如添加第三个画布用于显示游戏状态,只有在玩家状态发生变化时才进行更改。HTML可能如下所示:
<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>

2

抱歉,画布元素将有它的z-index,任何在其上绘制的内容都将在该层上。

如果您是指画布上的不同元素,则是的,任何绘制的东西都是绘制在之前所有内容的顶部。


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