I am trying make an paint app using ES6. But i am not getting proper position and line in canvas.
This line is not drawn in correct position, like top-left is formed when i click and from 0,0 corner of canvas.
As you can see Line is not starting from the point Cursor is pointing and this difference increases as we move from TOP-LEFT cornor to BOTTOM-RIGHT cornor.
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>
Thanks in advance.
The issue is 1 or both of 2 things
Your canvas is being displayed at 400x300 but it only has 300x150 pixels. Canvases have 2 sizes. One size is the size they are displayed set with CSS. The other is how many pixels which is usually set in code by setting canvas.width
and canvas.height
. The default number of pixels is 300x150
If you actually want them to be different sizes then you need to take that into account in your mouse code. The correct code is
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>
If you don't want them to be different sizes then you need to make the sizes match. I always set the size using CSS and then use code to make the canvas match that size.
Something like this
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;
}
Note that anytime you change the canvas size it will get cleared but in any case.
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>
A common reason to make them different sizes is to support HI-DPI displays. In that case though the mouse code can go back to the way it was if you use the canvas transform.
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;
}
and then set the transform before drawing
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>
note this line
this.saveData = this.context.getImageData(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
was wrong too. It should be
this.saveData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
clientWidth
and clientHeight
are the display size. width
and height
are the resolution (number of pixels in the canvas)