使用 ES6 无法在 Canvas 中获得正确的位置(为什么这段代码不能正常工作?)

Unable to get proper position in Canvas using ES6 (why isn't this code working properly?)

我正在尝试使用 ES6 制作一个绘画应用程序。但是我在 canvas.

中没有得到正确的位置和行

这条线没有画在正确的位置,就像我从 canvas 的 0,0 角点击左上角时形成的一样。

如您所见,线不是从光标指向的点开始的,并且当我们从 TOP-LEFT 角移动到 BOTTOM-RIGHT 角时,这种差异会增加。

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>

Here is link of code.

提前致谢。

问题是两件事中的一件或两件事

您的 canvas 显示为 400x300,但只有 300x150 像素。画布有 2 种尺寸。一种尺寸是用 CSS 设置的显示尺寸。另一种是多少像素,一般在代码中设置canvas.widthcanvas.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 设置大小,然后使用代码使 canvas 匹配该大小。

像这样

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;
}

请注意,任何时候您更改 canvas 大小,它都会被清除,但无论如何。

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 显示。在这种情况下,如果您使用 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);

clientWidthclientHeight 是显示大小。 widthheight是分辨率(canvas中的像素数)