Невозможно получить правильную позицию в Canvas с помощью ES6 (почему этот код не работает должным образом?) - PullRequest
0 голосов
/ 25 марта 2020

Я пытаюсь создать приложение для рисования, используя ES6. Но я не получаю правильную позицию и линию на холсте.

Эта линия рисуется не в правильном положении, как при формировании верхнего левого угла, когда я нажимаю и из 0,0 угла холста.

Как вы можете видеть, линия не начинается с точки, на которую указывает курсор, и эта разница увеличивается, когда мы переходим от ВЕРХНЕГО ЛЕГКОГО КРУГЛЫГО К НАРУЖНО-ВПРАВО.

const TOOL_LINE = 'line';

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Paint {
  constructor(canvasId) {

    this.canvas = document.getElementById(canvasId);
    this.context = canvas.getContext("2d");
  }
  set activeTool(tool) {
    this.tool = tool;
  }
  init() {
    this.canvas.onmousedown = e => this.onMouseDown(e);
  }
  onMouseDown(e) {
    this.saveData = this.context.getImageData(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
    this.canvas.onmousemove = e => this.onMouseMove(e);
    document.onmouseup = e => this.onMouseUp(e);
    this.startPos = this.getMouseCoordinatesCanvas(e, this.canvas);
  }
  onMouseMove(e) {
    this.currentPos = this.getMouseCoordinatesCanvas(e, this.canvas);
    switch (this.tool) {
      case TOOL_LINE:
        this.drawShape();
        break;
      default:
        break;
    }
  }
  onMouseUp(e) {
    this.canvas.onmousemove = null;
    document.onmouseup = null;
  }
  drawShape() {
    this.context.putImageData(this.saveData, 0, 0);
    this.context.beginPath();
    this.context.moveTo(this.startPos.x, this.startPos.y);
    this.context.lineTo(this.currentPos.x, this.currentPos.y);
    this.context.stroke();
  }
  getMouseCoordinatesCanvas(e, canvas) {
    let rect = canvas.getBoundingClientRect();
    let x = e.clientX - rect.left;
    let y = e.clientY - rect.top;
    return new Point(x, y);
  }
}

var paint = new Paint("canvas");
paint.activeTool = TOOL_LINE;
paint.init();

document.querySelectorAll("[data-tools]").forEach(
  item => {
    item.addEventListener("click", e => {
      let selectedTool = item.getAttribute("data-tools");
      paint.activeTool = selectedTool;

    });
  }
);
#Container {
  background-color: lime;
  height: 310px;
}

.toolbox,
#canvas {
  display: inline-block;
}

.toolbox {
  background-color: gray;
  padding: 0px 15px 15px 15px;
  left: 10px;
  top: 11px;
}

.group {
  margin: 5px 2px;
}

#line {
  transform: rotate(-90deg);
}

.ico {
  margin: 3px;
  font-size: 23px;
}

.item:hover,
.item.active {
  background-color: rgba(160, 160, 160, 0.5);
  color: white;
}

#canvas {
  background-color: white;
  margin: 5px;
  float: right;
  width: 400px;
  height: 300px;
}
<script src="https://kit.fontawesome.com/c1d28c00bc.js" crossorigin="anonymous"></script>
<div class="container">
  <div id="Container">
    <div class="toolbox">
      <center>
        <div class="group tools">
          <div class="item active" data-tools="line">
            <i class="ico far fa-window-minimize" id="line" title="Line"></i>
          </div>
        </div>
      </center>
    </div>
    <canvas id="canvas"></canvas>
  </div>
</div>

Вот ссылка на код.

Заранее спасибо.

1 Ответ

0 голосов
/ 27 марта 2020

Проблема в 1 или в двух вещах

Ваш холст отображается с разрешением 400x300, но имеет только 300x150 пикселей. Холсты имеют 2 размера. Один размер - это размер, который они показывают, установленный с CSS. Другой - это количество пикселей, которое обычно устанавливается в коде путем установки canvas.width и canvas.height. Количество пикселей по умолчанию составляет 300x150

Если вы действительно хотите, чтобы они были разных размеров, то вам необходимо учесть это в коде мыши. Правильный код:

  getMouseCoordinatesCanvas(e, canvas) {
    let rect = canvas.getBoundingClientRect();
    let x = (e.clientX - rect.left) * canvas.width  / rect.width;
    let y = (e.clientY - rect.top)  * canvas.height / rect.height;
    return new Point(x, y);
  }

const TOOL_LINE = 'line';

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Paint {
  constructor(canvasId) {

    this.canvas = document.getElementById(canvasId);
    this.context = canvas.getContext("2d");
  }
  set activeTool(tool) {
    this.tool = tool;
  }
  init() {
    this.canvas.onmousedown = e => this.onMouseDown(e);
  }
  onMouseDown(e) {
    this.saveData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
    this.canvas.onmousemove = e => this.onMouseMove(e);
    document.onmouseup = e => this.onMouseUp(e);
    this.startPos = this.getMouseCoordinatesCanvas(e, this.canvas);
  }
  onMouseMove(e) {
    this.currentPos = this.getMouseCoordinatesCanvas(e, this.canvas);
    switch (this.tool) {
      case TOOL_LINE:
        this.drawShape();
        break;
      default:
        break;
    }
  }
  onMouseUp(e) {
    this.canvas.onmousemove = null;
    document.onmouseup = null;
  }
  drawShape() {
    this.context.putImageData(this.saveData, 0, 0);
    this.context.beginPath();
    this.context.moveTo(this.startPos.x, this.startPos.y);
    this.context.lineTo(this.currentPos.x, this.currentPos.y);
    this.context.stroke();
  }
  getMouseCoordinatesCanvas(e, canvas) {
    let rect = canvas.getBoundingClientRect();
    let x = (e.clientX - rect.left) * canvas.width  / rect.width;
    let y = (e.clientY - rect.top)  * canvas.height / rect.height;
    return new Point(x, y);
  }
}

var paint = new Paint("canvas");
paint.activeTool = TOOL_LINE;
paint.init();

document.querySelectorAll("[data-tools]").forEach(
  item => {
    item.addEventListener("click", e => {
      let selectedTool = item.getAttribute("data-tools");
      paint.activeTool = selectedTool;

    });
  }
);
#Container {
  background-color: lime;
  height: 310px;
}

.toolbox,
#canvas {
  display: inline-block;
}

.toolbox {
  background-color: gray;
  padding: 0px 15px 15px 15px;
  left: 10px;
  top: 11px;
}

.group {
  margin: 5px 2px;
}

#line {
  transform: rotate(-90deg);
}

.ico {
  margin: 3px;
  font-size: 23px;
}

.item:hover,
.item.active {
  background-color: rgba(160, 160, 160, 0.5);
  color: white;
}

#canvas {
  background-color: white;
  margin: 5px;
  float: right;
  width: 400px;
  height: 300px;
}
<script src="https://kit.fontawesome.com/c1d28c00bc.js" crossorigin="anonymous"></script>
<div class="container">
  <div id="Container">
    <div class="toolbox">
      <center>
        <div class="group tools">
          <div class="item active" data-tools="line">
            <i class="ico far fa-window-minimize" id="line" title="Line"></i>
          </div>
        </div>
      </center>
    </div>
    <canvas id="canvas"></canvas>
  </div>
</div>

Если вы не хотите, чтобы они были разных размеров, вам нужно, чтобы размеры соответствовали. Я всегда устанавливаю размер с помощью CSS, а затем использую код, чтобы холст соответствовал этому размеру.

Примерно так:

function resizeCanvasToDisplaySize(canvas) {
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    canvas.width = width;
    canvas.height = height;
  }
  return needResize;
}

Обратите внимание, что при каждом изменении размера холста он получит очищено, но в любом случае.

const TOOL_LINE = 'line';

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

function resizeCanvasToDisplaySize(canvas) {
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    canvas.width = width;
    canvas.height = height;
  }
  return needResize;
}

class Paint {
  constructor(canvasId) {

    this.canvas = document.getElementById(canvasId);
    this.context = canvas.getContext("2d");
    resizeCanvasToDisplaySize(canvas);    
  }
  set activeTool(tool) {
    this.tool = tool;
  }
  init() {
    this.canvas.onmousedown = e => this.onMouseDown(e);
  }
  onMouseDown(e) {
    this.saveData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
    this.canvas.onmousemove = e => this.onMouseMove(e);
    document.onmouseup = e => this.onMouseUp(e);
    this.startPos = this.getMouseCoordinatesCanvas(e, this.canvas);
  }
  onMouseMove(e) {
    this.currentPos = this.getMouseCoordinatesCanvas(e, this.canvas);
    switch (this.tool) {
      case TOOL_LINE:
        this.drawShape();
        break;
      default:
        break;
    }
  }
  onMouseUp(e) {
    this.canvas.onmousemove = null;
    document.onmouseup = null;
  }
  drawShape() {
    this.context.putImageData(this.saveData, 0, 0);
    this.context.beginPath();
    this.context.moveTo(this.startPos.x, this.startPos.y);
    this.context.lineTo(this.currentPos.x, this.currentPos.y);
    this.context.stroke();
  }
  getMouseCoordinatesCanvas(e, canvas) {
    let rect = canvas.getBoundingClientRect();
    let x = (e.clientX - rect.left) * canvas.width  / rect.width;
    let y = (e.clientY - rect.top)  * canvas.height / rect.height;
    return new Point(x, y);
  }
}

var paint = new Paint("canvas");
paint.activeTool = TOOL_LINE;
paint.init();

document.querySelectorAll("[data-tools]").forEach(
  item => {
    item.addEventListener("click", e => {
      let selectedTool = item.getAttribute("data-tools");
      paint.activeTool = selectedTool;

    });
  }
);
#Container {
  background-color: lime;
  height: 310px;
}

.toolbox,
#canvas {
  display: inline-block;
}

.toolbox {
  background-color: gray;
  padding: 0px 15px 15px 15px;
  left: 10px;
  top: 11px;
}

.group {
  margin: 5px 2px;
}

#line {
  transform: rotate(-90deg);
}

.ico {
  margin: 3px;
  font-size: 23px;
}

.item:hover,
.item.active {
  background-color: rgba(160, 160, 160, 0.5);
  color: white;
}

#canvas {
  background-color: white;
  margin: 5px;
  float: right;
  width: 400px;
  height: 300px;
}
<script src="https://kit.fontawesome.com/c1d28c00bc.js" crossorigin="anonymous"></script>
<div class="container">
  <div id="Container">
    <div class="toolbox">
      <center>
        <div class="group tools">
          <div class="item active" data-tools="line">
            <i class="ico far fa-window-minimize" id="line" title="Line"></i>
          </div>
        </div>
      </center>
    </div>
    <canvas id="canvas"></canvas>
  </div>
</div>

Распространенная причина делать их разных размеров - поддержка дисплеев HI-DPI. В этом случае, хотя код мыши может go вернуться к тому, как это было, если вы используете преобразование canvas.

function resizeCanvasToDisplaySize(canvas) {
  const width = canvas.clientWidth * devicePixelRatio | 0;
  const height = canvas.clientHeight * devicePixelRatio | 0;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    canvas.width = width;
    canvas.height = height;
  }
  return needResize;
}

и затем установите преобразование перед рисованием

ctx.scale(devicePixelRatio, devicePixelRatio);

const TOOL_LINE = 'line';

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

function resizeCanvasToDisplaySize(canvas) {
  const width = canvas.clientWidth * devicePixelRatio | 0;
  const height = canvas.clientHeight * devicePixelRatio | 0;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    canvas.width = width;
    canvas.height = height;
  }
  return needResize;
}

class Paint {
  constructor(canvasId) {

    this.canvas = document.getElementById(canvasId);
    this.context = canvas.getContext("2d");
    resizeCanvasToDisplaySize(canvas);    
  }
  set activeTool(tool) {
    this.tool = tool;
  }
  init() {
    this.canvas.onmousedown = e => this.onMouseDown(e);
  }
  onMouseDown(e) {
    this.saveData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
    this.canvas.onmousemove = e => this.onMouseMove(e);
    document.onmouseup = e => this.onMouseUp(e);
    this.startPos = this.getMouseCoordinatesCanvas(e, this.canvas);
  }
  onMouseMove(e) {
    this.currentPos = this.getMouseCoordinatesCanvas(e, this.canvas);
    switch (this.tool) {
      case TOOL_LINE:
        this.drawShape();
        break;
      default:
        break;
    }
  }
  onMouseUp(e) {
    this.canvas.onmousemove = null;
    document.onmouseup = null;
  }
  drawShape() {
    this.context.setTransform(1, 0, 0, 1, 0, 0); // the default
    this.context.putImageData(this.saveData, 0, 0);
    this.context.scale(devicePixelRatio, devicePixelRatio);
    this.context.beginPath();
    this.context.moveTo(this.startPos.x, this.startPos.y);
    this.context.lineTo(this.currentPos.x, this.currentPos.y);
    this.context.stroke();
  }
  getMouseCoordinatesCanvas(e, canvas) {
    let rect = canvas.getBoundingClientRect();
    let x = (e.clientX - rect.left);
    let y = (e.clientY - rect.top);
    return new Point(x, y);
  }
}

var paint = new Paint("canvas");
paint.activeTool = TOOL_LINE;
paint.init();

document.querySelectorAll("[data-tools]").forEach(
  item => {
    item.addEventListener("click", e => {
      let selectedTool = item.getAttribute("data-tools");
      paint.activeTool = selectedTool;

    });
  }
);
#Container {
  background-color: lime;
  height: 310px;
}

.toolbox,
#canvas {
  display: inline-block;
}

.toolbox {
  background-color: gray;
  padding: 0px 15px 15px 15px;
  left: 10px;
  top: 11px;
}

.group {
  margin: 5px 2px;
}

#line {
  transform: rotate(-90deg);
}

.ico {
  margin: 3px;
  font-size: 23px;
}

.item:hover,
.item.active {
  background-color: rgba(160, 160, 160, 0.5);
  color: white;
}

#canvas {
  background-color: white;
  margin: 5px;
  float: right;
  width: 400px;
  height: 300px;
}
<script src="https://kit.fontawesome.com/c1d28c00bc.js" crossorigin="anonymous"></script>
<div class="container">
  <div id="Container">
    <div class="toolbox">
      <center>
        <div class="group tools">
          <div class="item active" data-tools="line">
            <i class="ico far fa-window-minimize" id="line" title="Line"></i>
          </div>
        </div>
      </center>
    </div>
    <canvas id="canvas"></canvas>
  </div>
</div>

обратите внимание, что эта строка

this.saveData = this.context.getImageData(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);

тоже неверна. Он должен быть

this.saveData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);

clientWidth и clientHeight - размер экрана. width и height - это разрешение (количество пикселей на холсте)

...