【问题标题】:Clearing canvas "layers" separately?分别清除画布“层”?
【发布时间】:2022-01-29 22:41:17
【问题描述】:

我和<canvas> 斗争了一段时间。我想创建一个动画/游戏,在不同的层上有很多不同的单元。

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

  • 页面上有一个<canvas>,
  • 使用document.createElement("canvas") 创建多个“层”,
  • 分别为“图层”设置动画/重新渲染。

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

这里的问题最终是它不会改变任何事情,因为一切都在我的主<canvas> 上绘制,当我在我的一个图层上执行clearRect 时,它什么也不做,因为像素也绘制在主画布除了给定的图层。如果我在主画布上运行clearRect,那么这些图层就没用了,因为每个图层都在主画布上,所以我回到起点,因为我正在清除整个画布并且图层根本没有分开。

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

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

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

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

【问题讨论】:

  • 在球的draw 中尝试使用ctx 而不是canvas2ctx

标签: javascript html canvas


【解决方案1】:

如果您从性能的角度来看事情,如果您使用单个可见的<canvas> 元素作为视觉输出,情况会更好。 不过,没有什么能阻止您在彼此堆叠的单独画布上做事。也许这里只是一个基本的误解。

你说:

当我在其中一个图层上执行 clearRect 时,它什么也不做 除了给定的图层之外,像素也在主画布上绘制

这不是真的。如果您将新清除的画布的内容绘制到另一个画布上,它不会用“无”覆盖目标画布。

看看这个例子:

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>

我们的主画布包含一个带有黑色圆圈的绿色背景,我们正在使用drawImage() 方法在其上绘制一个动态创建的、新清除的画布,这会产生一个带有黑色圆圈的绿色背景作为新的画布元素不包含任何要绘制的数据。它没有擦除主画布。

如果我们稍微改变一下例子,让第二个画布包含一个矩形,事情就会按预期工作:

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) 的绿色背景是两个独立的画布,我们最终要绘制到主画布上,这将带来一个重要的点:绘制顺序。

所以这会起作用:

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>

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

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

我们首先绘制包含透明背景和矩形的tempCanvasB,然后绘制带有纯绿色背景的tempCanvasA(覆盖整个画布)和圆形。由于没有透明像素,它会覆盖我们首先绘制的矩形。

以球为例。问题是您将球绘制到错误的画布上。在您的绘图功能中,您正在执行以下操作:

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

  ctx.drawImage(canvas2, 0, 0);

所以首先你清除ctx,然后调用ball 的draw 方法,该方法绘制到canvas2ctx,最后使用canvas2ctx 的内容将drawImage 绘制到ctx。

使用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();
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

    【解决方案2】:

    考虑一下您将多个画布堆叠在一起的方法听起来像是一种完成工作的有趣方法。我不建议以这种方式这样做,因此通过 JavaScript 处理多个层,然后每次都呈现新的东西。特别是如果你会使用动画,那么我相信多个不同步的画布会给你带来另一种头痛。

    然后您将执行以下操作:

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

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

    现在是您的代码: 归根结底,您的 ctx 和 canvas2ctx 处于完全相同的上下文中,因为它们来自同一个画布。无论如何,这没有多大意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-14
      • 2017-06-21
      • 2012-07-26
      • 2012-07-19
      • 2014-06-23
      • 2014-11-07
      相关资源
      最近更新 更多