分别清除canvas"layers"?

Clearing canvas "layers" separately?

我已经和 <canvas> 斗争了一段时间。我想创建一个 animation/game,在不同的层上有很多不同的单元。

由于<canvas>仅限于一种情况,我的方法如下:

但由于一个怪癖,这种方法似乎无法正常工作 - 为了在我的主 canvas 上堆叠“层”,我正在做 realCanvas.drawImage(layerCanvas, 0, 0);。否则图层不会被渲染。

这里的问题最终是它不会改变任何东西,因为一切都在我的主要 <canvas> 上绘制,当我在我的一个图层上 clearRect 时,它没有像像素那样做任何事情除了给定层之外,还绘制在主要 canvas 上。如果我 运行 clearRect 在 main canvas 上,那么这些层是无用的,因为每一层都在 main canvas 上,所以我回到起点,因为我正在清理整个canvas而且图层完全没有分开。

有没有办法轻松解决?我觉得我错过了一些明显的东西。

这里有一个例子,如何在不触及此处背景矩形的情况下清除蓝球轨迹?光标下应该只有一个蓝色球。请注意,这是一个非常简化的示例,我将有多个蓝色球和多个其他图层。我只想知道我到底是怎么在canvas中只清除一层的。请注意,我不想使用多个 <canvas> 元素,也不想使用任何 libs/engines,因为我正在尝试通过此学习 canvas。我知道许多应用程序只使用一个 canvas html 元素,许多层并以某种方式分别为它们设置动画。

来源:https://jsfiddle.net/rpmf4tsb/

尝试在 ctx.clearRect(0,0, canvas.width, canvas.height); 下添加 canvas2ctx.clearRect(0,0, canvas.width, canvas.height);,它按预期工作,但所有层都被清除,不仅仅是带球的层...

考虑一下您将多个 canvas 堆叠在一起的方法听起来是一种完成工作的有趣方法。我不建议以这种方式执行此操作,因此通过 JavaScript 处理多个图层,然后每次都呈现新的内容。特别是如果你会用到动画,那么相信多个不同步canvas会让你又头疼

然后您将执行以下操作:

  1. 使用 clearRect 清除 canvas。
  2. 在一个迭代中绘制每个层彼此之上

我希望这个理论解释对您有所帮助。

现在你的代码:在一天结束时你的 ctx 和 canvas2ctx 处于完全相同的上下文中,因为它们来自相同的 canvas。无论如何,这没有多大意义。

如果您从表演 point-of-view 来看事物,如果您使用单个可见 <canvas> 元素作为视觉输出,情况会更好。 没有什么能阻止你在单独的 canvases 上做事,尽管你堆叠在一起。也许这里只是一个基本的误解。

你说:

and when I do clearRect on one of my layers it does nothing as the pixels are also drawn on the main canvas in addition to given layer

那不是真的。如果您将新清除的 canvas 的内容绘制到另一个 canvas 上,它不会用 'nothing'.

覆盖目标 canvas

看看这个例子:

let canvas = document.getElementById("canvas")
let ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 10;
ctx.arc(canvas.width / 2, canvas.height / 2, 50, 0, 2 * Math.PI);
ctx.stroke();

let tempCanvas = document.createElement("canvas");
let tempContext = tempCanvas.getContext("2d");
tempContext.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height);
<canvas id="canvas"></canvas>

我们的主要 canvas 包含一个带有黑色圆圈的绿色背景,我们正在利用 drawImage() 方法绘制一个动态创建的、新清除的 canvas 到上面,这导致带有黑色圆圈的绿色背景,因为新的 canvas 元素不包含任何要绘制的数据。它没有擦除主要canvas。

如果我们稍微更改示例,那么第二个 canvas 包含一个矩形,事情将按预期工作:

let canvas = document.getElementById("canvas")
let ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 10;
ctx.arc(canvas.width / 2, canvas.height / 2, 50, 0, 2 * Math.PI);
ctx.stroke();

let tempCanvas = document.createElement("canvas");
let tempContext = tempCanvas.getContext("2d");
tempContext.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
tempContext.strokeRect(tempCanvas.width / 2 - 60, tempCanvas.height / 2 - 60, 120, 120);
ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height);
<canvas id="canvas"></canvas>

现在,如果我们假设带有圆圈 (tempCanvasA) 和矩形 (tempCanvasB) 的绿色背景是两个独立的 canvas 元素,我们最终想要绘制到一个主元素 canvas 它将显示重点:绘图顺序.

所以这会起作用:

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

let tempCanvasA = document.createElement("canvas");
let tempContextA = tempCanvasA.getContext("2d");
tempContextA.fillStyle = "green";
tempContextA.fillRect(0, 0, tempCanvasA.width, tempCanvasA.height);
tempContextA.beginPath();
tempContextA.lineWidth = 10;
tempContextA.arc(tempCanvasA.width / 2, tempCanvasA.height / 2, 50, 0, 2 * Math.PI);
tempContextA.stroke();

let tempCanvasB = document.createElement("canvas");
let tempContextB = tempCanvasB.getContext("2d");
tempContextB.strokeRect(tempCanvasB.width / 2 - 60, tempCanvasB.height / 2 - 60, 120, 120);


ctx.drawImage(tempCanvasA, 0, 0, canvas.width, canvas.height);
ctx.drawImage(tempCanvasB, 0, 0, canvas.width, canvas.height);
<canvas id="canvas"></canvas>

虽然失败了:

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

let tempCanvasA = document.createElement("canvas");
let tempContextA = tempCanvasA.getContext("2d");
tempContextA.fillStyle = "green";
tempContextA.fillRect(0, 0, tempCanvasA.width, tempCanvasA.height);
tempContextA.beginPath();
tempContextA.lineWidth = 10;
tempContextA.arc(tempCanvasA.width / 2, tempCanvasA.height / 2, 50, 0, 2 * Math.PI);
tempContextA.stroke();

let tempCanvasB = document.createElement("canvas");
let tempContextB = tempCanvasB.getContext("2d");
tempContextB.strokeRect(tempCanvasB.width / 2 - 60, tempCanvasB.height / 2 - 60, 120, 120);

ctx.drawImage(tempCanvasB, 0, 0, canvas.width, canvas.height);
ctx.drawImage(tempCanvasA, 0, 0, canvas.width, canvas.height);
<canvas id="canvas"></canvas>

矩形不见了!为什么会失败?因为我们改变了顺序,所以我们将 canvases 绘制到主要的 canvas 上。在后一个例子中:

ctx.drawImage(tempCanvasB, 0, 0, canvas.width, canvas.height);
ctx.drawImage(tempCanvasA, 0, 0, canvas.width, canvas.height);

我们首先绘制 tempCanvasB,其中包含透明背景和矩形,然后绘制 tempCanvasA 纯绿色背景 - 覆盖整个 canvas - 和圆圈。由于没有透明像素,它将覆盖我们首先绘制的矩形。

以球为例。问题是你把球拉错了 canvas。在你的绘图函数中,你正在这样做:

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x = e.clientX;
  ball.y = e.clientY;

  ctx.drawImage(canvas2, 0, 0);

所以首先你清除ctx,然后调用球的绘制方法绘制到canvas2ctx,最后将图像绘制到ctx,内容为canvas2ctx

而是使用 drawImage()

在 之后将球拉到主要 ctx

例如

// helper functions

function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min)
}

// canvas
let firstRender = true;
var canvas = document.getElementById('canvas');
canvas.width = window.innerWidth - 50;
canvas.height = window.innerHeight - 50;
let ctx = canvas.getContext('2d');

// virtual canvas for rectangles layer
let canvas2 = document.createElement("canvas");
canvas2.width = window.innerWidth - 50;
canvas2.height = window.innerHeight - 5;
let canvas2ctx = canvas2.getContext("2d");


let ball = {
    x: 100,
    y: 100,
    vx: 5,
    vy: 2,
    radius: 25,
    color: 'blue',
    draw: function() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fillStyle = this.color;
        ctx.fill();
    }
};

function draw(e) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(canvas2, 0, 0);
    ball.draw();
    ball.x = e.clientX;
    ball.y = e.clientY;
    if (firstRender) {
        drawRandomRectangles()
        firstRender = false;
    }
}

function drawRandomRectangles() {
    for (i = 0; i < 50; i++) {
        canvas2ctx.beginPath();
        canvas2ctx.rect(randomInt(0, window.innerWidth - 50), randomInt(0, window.innerWidth - 50), randomInt(5, 20), randomInt(5, 20));
        canvas2ctx.stroke();
    }
}

canvas.addEventListener('mousemove', function(e) {
    draw(e);
});


ball.draw();
<canvas id="canvas"></canvas>