I am working on a simple drawing tool using JavaScript. However I have a problem with my draw() function. The line is always drawn slightly below the center of the mouse. May I please know what is my mistake here? I want the line to always be drawn at the center of the mouse as it moves. In my setPosition() function, does e.pageX and e.pageY actually maps the center of the mouse as x and y coordinates?
<!doctype html>
<html>
<head>
<style>
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<div class="controls">
<button class="clear">Clear</button> <span>Color
<input type="color" value="#ffff00" id="penColor"></span>
<span>Width
<input type="range" min="1" max="20" value="10" id="penWidth"></span>
</div>
<canvas id="canvas"></canvas>
<script>
let penColor = document.getElementById("penColor");
let penWidth = document.getElementById("penWidth");
let canvas = document.getElementById("canvas");
canvas.width = 700;
canvas.height = 700;
let context = canvas.getContext("2d");
let clearButton = document.querySelector(".clear");
let position = {
x: null,
y: null
}
let initialization = (e) => {
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseenter", setPosition)
canvas.addEventListener("mousemove", setPosition)
}
window.onload = initialization;
let setPosition = (e) => {
position.x = e.pageX;
position.y = e.pageY;
}
clearButton.addEventListener("click", (e) => {
let confirmation = confirm("Are you sure you want to clear the canvas?");
let result = confirmation ? true : false;
if (result) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
})
let draw = (e) => {
if (e.buttons !== 1) return;
context.beginPath();
context.moveTo(position.x, position.y);
setPosition(e);
context.lineTo(position.x, position.y);
context.lineWidth = penWidth.value;
context.strokeStyle = penColor.value;
context.lineCap = "round";
context.stroke();
}
</script>
</body>
</html>
MouseEvent.pageX
The pageX read-only property of the MouseEvent interface returns the X (horizontal) coordinate (in pixels) at which the mouse was clicked, relative to the left edge of the entire document.
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX
so that is not the X position on the canvas, you have to substract the canvas position.
e.pageX - canvas.offsetLeft;
e.pageY - canvas.offsetTop;
let canvas = document.getElementById("canvas");
canvas.width = canvas.height = 200;
let context = canvas.getContext("2d");
let position = { x: null, y: null }
let setPosition = (e) => {
position.x = e.pageX - canvas.offsetLeft;
position.y = e.pageY - canvas.offsetTop;
}
let draw = (e) => {
if (e.buttons !== 1) return;
context.beginPath();
context.moveTo(position.x, position.y);
setPosition(e);
context.lineTo(position.x, position.y);
context.stroke();
}
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseenter", setPosition)
canvas.addEventListener("mousemove", setPosition)
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
Related
I am trying to create a whiteboarding web app using HTML5 and Canvas.
I have implement a simple pen and paintbrush shaped pen with help from this brilliant article:
http://perfectionkills.com/exploring-canvas-drawing-techniques/
My issue is withe dotted line pen and highlighter pen.
The dotted line looks like a simple unbroken line if the mouse moves slowly, and with large gaps if moved quickly. What I want is a consistently spaced dotted line.
I tried setting the context.setLineDash but this has no effect on the result.
I then tried to calculate a minimum distance between the last point and current point and draw if over the dot gap lenth but this also does not seemeingly affect the result.
Here is my code:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var lastPoint;
var isDrawing = false;
context.lineWidth = 4;
context.lineJoin = context.lineCap = 'round';
canvas.onmousedown = function(e) {
isDrawing = true;
lastPoint = {
x: e.clientX,
y: e.clientY
};
lastPoint = {
x: e.offsetX,
y: e.offsetY
};
};
canvas.onmousemove = function(e) {
if (!isDrawing) return;
context.beginPath();
context.strokeStyle = 'red';
context.fillStyle = 'red';
mx = e.clientX; // mouse pointer and stroke path is off if use this
my = e.clientY;
mx = e.offsetX; // mouse pointer and stroke path match using this
my = e.offsetY;
context.setLineDash([5, 25]);
xlen = Math.abs(mx - lastPoint.x) + context.lineWidth;
ylen = Math.abs(my - lastPoint.y) + context.lineWidth;
gap = Math.sqrt((ylen * ylen) + (xlen * xlen));
if (gap >= 5) {
context.moveTo(lastPoint.x, lastPoint.y);
context.lineTo(mx, my);
context.stroke();
lastPoint = {
x: mx,
y: my
};
}
};
canvas.onmouseup = function() {
isDrawing = false;
};
html,body,canvas
{
width: 100%;
height: 100%;
margin: 0;
}
<canvas id="canvas" ></canvas>
The result is this:
With the highlighter, I get overlapping points which give dark spots on the path. The code for this is:
context.globalAlpha = 0.3;
context.moveTo(lastPoint.x, lastPoint.y);
context.lineTo(mx, my);
context.stroke();
lastPoint = { x: mx, y: my };
The result:
const map = document.getElementById('map')
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const grid = document.getElementById('grid')
function resize() {
canvas.width = map.offsetWidth
canvas.height = map.offsetHeight
ctx.width = map.offsetWidth
ctx.height = map.offsetHeight
}
resize();
grid.appendChild(canvas)
canvas.style.gridColumn = 2
canvas.style.gridRow = 1
let pos = { x: 0, y: 0 };
window.addEventListener('resize', resize);
document.addEventListener('mousemove', draw);
document.addEventListener('mousedown', setPosition);
document.addEventListener('mouseenter', setPosition);
function setPosition(e) {
pos.x = e.clientX;
pos.y = e.clientY;
}
function draw(e) {
if (e.buttons !== 1) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = '#c0392b';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
}
Heres the code that generates the canvas relative to the size of a picture and allows the user to draw on the canvas. I've looked over another StackOverflow post with the same problem but no relevant answers. I know that the cause of the problem is that the canvas is stretched from it's standard proportion of 300 x 150 and is drawing at the correct position mathematically but not physically. How do I fix this?
I have a canvas that is adding dynamically to the page on on load.
I want to draw user's mouse path on the canvas, but I found that if I clear the canvas before drawing, it will draw smooth lines, otherwise, it will draw ugly lines like following screenshot!
To test the problem, please uncomment first line of draw_on_canvas function in the code to see the difference.
$(document).ready(function() {
//Create DRAWING environment
var canvasWidth = 400;
var canvasHeight = 200;
var drawn_shape_list = [];
var current_shape_info = {};
var is_painting = false;
function add_path_to_drawn_shape_list() {
if (current_shape_info.path && current_shape_info.path.length > 0) {
drawn_shape_list.push(current_shape_info);
}
current_shape_info = {};
}
function add_path(x, y) {
current_shape_info.color = "#000000";
current_shape_info.size = 2;
if (!current_shape_info.path) {
current_shape_info.path = [];
}
current_shape_info.path.push({
"x": x,
"y": y
});
}
function draw_on_canvas() {
//Uncomment following line to have smooth drawing!
//context.clearRect(0, 0, context.canvas.width, context.canvas.height); //clear canvas
context.strokeStyle = current_shape_info.color;
context.lineWidth = current_shape_info.size;
context.beginPath();
context.moveTo(current_shape_info.path[0].x, current_shape_info.path[0].y);
for (var i = 1; i < current_shape_info.path.length; i++) {
context.lineTo(current_shape_info.path[i].x, current_shape_info.path[i].y);
}
context.stroke();
}
//Create canvas node
var canvas_holder = document.getElementById('canvas_holder');
canvas = document.createElement('canvas');
canvas.setAttribute('width', canvasWidth);
canvas.setAttribute('height', canvasHeight);
canvas.setAttribute('id', 'whitboard_canvas');
canvas_holder.appendChild(canvas);
if (typeof G_vmlCanvasManager != 'undefined') {
canvas = G_vmlCanvasManager.initElement(canvas);
}
context = canvas.getContext("2d");
$('#canvas_holder').mousedown(function(e) {
var mouseX = e.pageX - this.offsetLeft;
var mouseY = e.pageY - this.offsetTop;
is_painting = true;
add_path(mouseX, mouseY, false);
draw_on_canvas();
});
$('#canvas_holder').mousemove(function(e) {
if (is_painting) {
var mouseX = e.pageX - this.offsetLeft;
var mouseY = e.pageY - this.offsetTop;
var can = $('#whitboard_canvas');
add_path(mouseX, mouseY, true);
draw_on_canvas();
}
});
$('#canvas_holder').mouseup(function(e) {
is_painting = false;
add_path_to_drawn_shape_list();
});
$('#canvas_holder').mouseleave(function(e) {
is_painting = false;
add_path_to_drawn_shape_list();
});
});
#canvas_holder {
border: solid 1px #eee;
}
canvas {
border: solid 1px #ccc;
}
<HTML>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="canvas_holder"></div>
</body>
</HTML>
You can see an example of my code here with two canvas.
I have tried context.lineJoin = "round"; and context.lineCap = 'round';, but the result didn't change.
Is it normal canvas behavior or I should set something?
how to draw smooth lines on canvas without clearing it?
You don't. Clearing and redrawing is the way to go.
Is it normal canvas behavior
Yes completely. Unless you perform some action that should clear the canvas, it won't be cleared. So when you draw multiple times over the same area using semi transparent color, the pixels will become darker and darker.
Don't be afraid of performances, having to deal with the compositing of previous drawings may even be slower than drawing a single more complex path.
One thing you can do to improve performances is to use a single Path, so that at every frame only a single paint operation occurs:
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const path = new Path2D();
const mouse = {};
function draw() {
// clear all
ctx.clearRect( 0, 0, canvas.width, canvas.height );
// draw the single path
ctx.stroke( path );
// tell we need to redraw next frame
mouse.dirty = false;
}
canvas.onmousedown = (evt) => {
mouse.down = true;
// always use the same path
path.moveTo( evt.offsetX, evt.offsetY );
};
document.onmouseup = (evt) => {
mouse.down = false;
};
document.onmousemove = (evt) => {
if( mouse.down ) {
const rect = canvas.getBoundingClientRect();
path.lineTo( evt.clientX - rect.left, evt.clientY - rect.top );
}
if( !mouse.dirty ) {
mouse.dirty = true;
requestAnimationFrame(draw);
}
};
canvas { border: 1px solid }
<canvas id="canvas" width="500" height="500"></canvas>
If you need to have different path styles, then you can create one path per style.
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const makePath = (color) => ({
color,
path2d: new Path2D()
});
const pathes = [makePath('black')];
const mouse = {};
function draw() {
// clear all
ctx.clearRect( 0, 0, canvas.width, canvas.height );
pathes.forEach( (path) => {
// draw the single path
ctx.strokeStyle = path.color;
ctx.stroke( path.path2d );
} );
// tell we need to redraw next frame
mouse.dirty = false;
}
document.getElementById('inp').onchange = (evt) =>
pathes.push( makePath( evt.target.value ) );
canvas.onmousedown = (evt) => {
mouse.down = true;
const path = pathes[ pathes.length - 1 ].path2d;
// always use the same path
path.moveTo( evt.offsetX, evt.offsetY );
};
document.onmouseup = (evt) => {
mouse.down = false;
};
document.onmousemove = (evt) => {
if( mouse.down ) {
const rect = canvas.getBoundingClientRect();
const path = pathes[ pathes.length - 1 ].path2d;
path.lineTo( evt.clientX - rect.left, evt.clientY - rect.top );
}
if( !mouse.dirty ) {
mouse.dirty = true;
requestAnimationFrame(draw);
}
};
canvas { border: 1px solid }
<input type="color" id="inp"><br>
<canvas id="canvas" width="500" height="500"></canvas>
Im trying to make so when you click on a canvas, there comes a circle at the mouse's cordinates.
When i click the canvas the circle appears maybe 300 pixels to low.
I tried changing from pageX/Y, clientX/Y and screenX/Y.
<canvas id="canvas" class="canvas" width="300px" height="200px" style="border: 1px solid #000; Padding-bottom: 100px;">
</canvas>
<script>
window.onload = function()
{
var canvas = document.getElementById("canvas");
addEventListener("mousedown", drawCircle)
}
function drawCircle(event)
{
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
x = event.pageX
y = event.pageY
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, y, 50, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill()
}
</script>
I expect the circle to appear at the mouse's cordinates.
You need to account for the position of the canvas itself. it looks like your code assumes the canvas is always in the top left of the screen.
There is also some issues with your canvas element. The width and height should just be numbers (no "px" required). The bottom padding is making the bottom half of the canvas inaccessible.
window.onload = function() {
var canvas = document.getElementById("canvas");
addEventListener("mousedown", drawCircle)
}
function drawCircle(event) {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var rect = canvas.getBoundingClientRect();
x = event.clientX - rect.left;
y = event.clientY - rect.top;
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, y, 50, 0, 2 * Math.PI, true);
ctx.stroke();
ctx.fill()
}
<canvas id="canvas" class="canvas" width="300" height="200" style="border: 1px solid #000;">
</canvas>
Possibly being offset by other elements on the page around the canvas. Try something like this to get x/y relative to canvas:
<canvas id="canvas" class="canvas"></canvas>
<script>
window.onload = function () {
var canvas = document.getElementById("canvas");
canvas.width = 300;
canvas.height = 200;
addEventListener("mousedown", drawCircle)
}
function drawCircle(event) {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var rect = canvas.getBoundingClientRect();
x = (event.clientX - rect.left)
y = (event.clientY - rect.top)
//x = event.pageX
//y = event.pageY
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, y, 50, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill()
}
</script>
https://jsfiddle.net/1z5modk2/1/
Instead of using pageX and pageY, use offsetX and offsetY
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX
I've got a canvas in HTML and when I draw on it when the browser window is at my standard size it works fine however when you start to scale the window (i.e shrink it), the drawing on the canvas doesn't appear where it's meant to.
var mousePressed = false;
var canvas = document.getElementById("canvas");
var lastX;
var lastY;
var colour = "black";
var size = 3;
function clear1() {
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
}
canvas.addEventListener("mousedown", (function(e) {
var xpos = e.pageX - this.offsetLeft;
var ypos = e.pageY - this.offsetTop;
mousePressed = true;
draw(xpos, ypos, false);
}));
canvas.addEventListener("mouseup", (function(e) {
mousePressed = false;
}));
canvas.addEventListener("mousemove", (function(e) {
var xpos = e.pageX - this.offsetLeft;
var ypos = e.pageY - this.offsetTop;
if (mousePressed) {
draw(xpos, ypos, true);
}
}));
function draw(x, y, isDown) {
if (isDown) {
context = canvas.getContext("2d");
context.beginPath();
context.strokeStyle = colour;
context.lineWidth = size;
context.lineJoin = "round";
context.moveTo(lastX, lastY);
context.lineTo(x, y);
context.closePath();
context.stroke();
}
lastX = x;
lastY = y;
}
#canvas {
border: 2px solid black;
background-color: white;
margin: auto;
display: block;
width: 200px;
height: 100px;
}
<canvas id="canvas" width="200" height="100">
</canvas>
<button onclick="clear1()" type="Submit1">Clear</button>
<input name="Submit" id="submitButton" type="submit" onclick="sendPayment()" value="Send Payment">
From Kaiido's comment:
Don't set canvas width/height through CSS, use it's own width and height properties directly