Странное поведение при повторной отрисовке прямоугольника в классе JS

Первый раз пытаюсь работать с ООП, пытаюсь создать класс который будет рисовать на canvas. Почему при повторном выполнении метода drawRect() прямоугольник рисуется на половину canvas? Объясните это странное поведение.

Ссылка на codepen: https://codepen.io/ilyacnt/pen/KKZzzRL

class CanvasRenderer {
  canvasElement = null;
  canvasWidth = 0;
  canvasHeight = 0;
  ctx = null;

  constructor(canvasElement) {
    this.canvasElement = canvasElement;
    this.ctx = canvasElement.getContext("2d");
    this.canvasHeight = canvasElement.height;
    this.canvasWidth = canvasElement.width;
  }

  clear() {
    this.ctx.rect(0, 0, this.canvasHeight, this.canvasWidth);
    this.ctx.fillStyle = "white";
    this.ctx.fill();
  }
}

class Brush extends CanvasRenderer {
  currentFillColor = "black";

  constructor(canvasElement) {
    super(canvasElement);
  }

  set fillColor(color) {
    this.currentFillColor = color;
  }

  drawRect() {
    this.ctx.fillStyle = this.currentFillColor;
    this.ctx.rect(450, 300, 50, 50);
    this.ctx.fill();
  }
}

const canvasElement = document.getElementById("canvas");
const brush = new Brush(canvasElement);

brush.drawRect();
brush.clear();
brush.drawRect();
<canvas id="canvas"></canvas>


Ответы (1 шт):

Автор решения: Вася Воронцов

Прямоугольник рисуется на всю ширину и всю высоту, потому что в CanvasRenderer.clear() создаётся контур прямоугольника (rect), который заливается в последующих вызовах fill.

Перед новой заливкой (fill) или выделения контуров (stroke) нужно вызвать beginPath, чтобы эти действия применялись к ожидаемым очертаниям:

class CanvasRenderer {
  canvasElement = null;
  canvasWidth = 0;
  canvasHeight = 0;
  ctx = null;

  constructor(canvasElement) {
    this.canvasElement = canvasElement;
    this.ctx = canvasElement.getContext("2d");
    this.canvasHeight = canvasElement.height;
    this.canvasWidth = canvasElement.width;
  }

  clear() {
    this.ctx.beginPath();
    this.ctx.rect(0, 0, this.canvasHeight, this.canvasWidth);
    this.ctx.fillStyle = "white";
    this.ctx.fill();
  }
}

class Brush extends CanvasRenderer {
  currentFillColor = "black";

  constructor(canvasElement) {
    super(canvasElement);
  }

  set fillColor(color) {
    this.currentFillColor = color;
  }

  drawRect() {
    this.ctx.beginPath();
    this.ctx.fillStyle = this.currentFillColor;
    this.ctx.rect(450, 300, 50, 50);
    this.ctx.fill();
  }
}

const canvasElement = document.getElementById("canvas");
const brush = new Brush(canvasElement);

brush.drawRect();
brush.clear();
brush.drawRect();
<canvas id="canvas" width="900" height="600"></canvas>

Также можно использовать готовые решения для заливки (fillRect) и очистки (clearRect) прямоугольников:

class CanvasRenderer {
  canvasElement = null;
  canvasWidth = 0;
  canvasHeight = 0;
  ctx = null;

  constructor(canvasElement) {
    this.canvasElement = canvasElement;
    this.ctx = canvasElement.getContext("2d");
    this.canvasHeight = canvasElement.height;
    this.canvasWidth = canvasElement.width;
  }

  clear() {
    this.ctx.clearRect(0, 0, this.canvasHeight, this.canvasWidth);
  }
}

class Brush extends CanvasRenderer {
  currentFillColor = "black";

  constructor(canvasElement) {
    super(canvasElement);
  }

  set fillColor(color) {
    this.currentFillColor = color;
  }

  drawRect() {
    this.ctx.fillStyle = this.currentFillColor;
    this.ctx.fillRect(450, 300, 50, 50);
  }
}

const canvasElement = document.getElementById("canvas");
const brush = new Brush(canvasElement);

brush.drawRect();
brush.clear();
brush.drawRect();
<canvas id="canvas" width="900" height="600"></canvas>

→ Ссылка