I'm trying to let users draw rectangles on a canvas using the mouse and i've been able to get it to work to some extent.The users can draw the rectangles using the mouse but it only shows after the mouseup event but i also want the users to also see the rectangles while drawing it on mousemove event. How can i achieve this and at the same time let users draw multiple rectangles which currently works but i want them to see it while drawing the rectangles.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#container {
/*background-color: lime;*/
width: 150px;
height: 150px;
cursor: pointer;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
#heatmapContainer {
border: 1px solid red;
}
</style>
</head>
<body>
<div class="heatmapWrapper">
<div id="heatmapContainer" style="height: 4205px; width: 1278px">
<div id="heatmap1" class="heatmapTile" style="height: 4205px; position: relative">
<canvas id="myCanvas" class="heatmap-canvas" width="1278" height="4205" style="position: absolute; left: 0px; top: 0px"></canvas>
</div>
</div>
</div>
<script>
var canvas = document.getElementById('myCanvas'),
elemLeft = canvas.offsetLeft,
elemTop = canvas.offsetTop,
context = canvas.getContext('2d');
let start = {};
var mouseDown = false;
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(),
scaleX = canvas.width / rect.width,
scaleY = canvas.height / rect.height;
return {
x: (evt.clientX - rect.left) * scaleX,
y: (evt.clientY - rect.top) * scaleY,
};
}
function startRect(e) {
start = getMousePos(canvas, e);
mouseDown = true;
}
window.addEventListener('mousedown', startRect);
function endRect(e) {
let {
x,
y
} = getMousePos(canvas, e);
context.strokeStyle = 'blue';
context.strokeRect(start.x, start.y, x - start.x, y - start.y);
mouseDown = false;
}
function drawSquare(e) {
if (!mouseDown) return;
// creating a square
var width = Math.abs(start.x - canvas.getBoundingClientRect().offsetLeft);
var height = Math.abs(start.y - canvas.getBoundingClientRect().offsetLeft);
context.beginPath();
context.rect(start.x, start.y, width, height);
context.strokeStyle = 'blue';
context.stroke();
}
window.addEventListener('mouseup', endRect);
window.addEventListener('mousemove', drawSquare);
/**End Drawing a rectangle on the canvas **/
</script>
</body>
</html>
We need to make something different, instead of drawing on the fly we need to add the rectangles to an array, then clean the canvas and draw them on demand, that way we can also draw additional elements, on the sample below I draw a red rectangle while moving and blue ones once we release the mouse
var canvas = document.getElementById('myCanvas')
var context = canvas.getContext('2d')
let start = {}
let rects = [{x: 9, y: 9, width: 50, height: 30}]
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect()
return {
x: (evt.clientX - rect.left) * canvas.width / rect.width,
y: (evt.clientY - rect.top) * canvas.height / rect.height,
};
}
function startRect(e) {
start = getMousePos(canvas, e);
}
function endRect(e) {
let { x, y } = getMousePos(canvas, e)
rects.push({x: start.x, y: start.y, width: x - start.x, height: y - start.y})
start = {}
draw()
}
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height)
rects.forEach(rect => {
context.beginPath();
context.rect(rect.x, rect.y, rect.width, rect.height);
context.strokeStyle = 'blue';
context.stroke();
});
}
function drawMove(e) {
if (start.x) {
draw()
let { x, y } = getMousePos(canvas, e)
context.beginPath();
context.rect(start.x, start.y, x - start.x, y - start.y);
context.strokeStyle = 'red';
context.stroke();
context.beginPath();
context.arc(start.x, start.y, 5, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.arc(x, y, 5, 0, 2 * Math.PI);
context.fill();
}
}
draw()
window.addEventListener('mouseup', endRect)
window.addEventListener('mousedown', startRect)
window.addEventListener('mousemove', drawMove)
canvas { border: solid }
<canvas id="myCanvas" width="400" height="400"></canvas>
Related
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>
This question already has answers here:
clearRect function doesn't clear the canvas
(5 answers)
Closed 4 years ago.
I'm creating a canvas game that has balls that bounce off each other.
I would like the balls to have their own skins where a background image is put onto the arc element.
When the ball isn't bouncing, the image clips just fine and is circular, like the arc. However, when I start to animate the ball, it simply wouldn't move at all because the clip function doesn't allow the image or the arc to be redrawn.
That's when I discovered the save and restore functions in the canvas that have allowed the clip method to be used while animating.
The issue with this is that part of the image doesn't clip properly. Only half of the animation is circular and the other half is the rectangular image. I've tried adjusting the position of the image but this did not lead to the desired result.
I'm not sure why the code is behaving like this and how to fix it so that it's simply an animating ball with an image background.
If someone has some insight into this and perhaps a solution, that would be very much appreciated.
Here is the code and a snippet below:
const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.imgX = this.xPos - this.radius;
}
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
ctx.save();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
};
Balls.prototype.motion = function() {
this.imgX = this.imgX + 1;
this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
object.render();
object.motion();
ctx.restore();
}
setInterval(animate, 50);
body {
background-color: grey;
}
#canvas {
background-color: white;
}
#mod {
border-radius: 100%
}
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src='practice.js'></script>
<link rel="stylesheet" type="text/css" href="practice.css">
</head>
<body>
<canvas id="canvas" height="200" width="800" />
</body>
</html>
You need to call ctx.beginPath(), otherwise, every call to arc() are added to the same and only sub-path. This means that at the end, you are clipping a weird Path made from a lot of arcs: ASCII representation of the Path after 5 frames : ((((( )
const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.imgX = this.xPos - this.radius;
}
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
ctx.save();
// begin a new sub-path
ctx.beginPath();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
};
Balls.prototype.motion = function() {
this.imgX = this.imgX + 1;
this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
object.render();
object.motion();
ctx.restore();
requestAnimationFrame(animate);
}
animate();
body {
background-color: grey;
}
#canvas {
background-color: white;
}
#mod {
border-radius: 100%
}
<canvas id="canvas" height="200" width="800" />
Also note that for circle masking, you would be better to use compositing than clipping, compositing handles better antialiasing, and doesn't require expensive save/restore.
const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
constructor(xPos, yPos, radius) {
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
this.imgX = this.xPos - this.radius;
}
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
// begin a new sub-path
ctx.beginPath();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.globalCompositeOperation = 'source-in';
ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
ctx.globalCompositeOperation = 'source-over';
};
Balls.prototype.motion = function() {
this.imgX = this.imgX + 1;
this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
object.render();
object.motion();
requestAnimationFrame(animate);
}
animate();
body {
background-color: grey;
}
#canvas {
background-color: white;
}
#mod {
border-radius: 100%
}
<canvas id="canvas" height="200" width="800" />
I have one canvas element that when positioned with css, fails to register mouse clicks and move elements drawn within canvas. If you do not position the canvas the mouse clicks register and selected elements will get moved but I need to be able to position this canvas layer with left and top so I can put another canvas underneath yet retain my mouse click and move ability.
This is what I am using to get my x/y click coordinates
var br = canvas.getBoundingClientRect(); //bounding rectangle
var mousex = parseInt(e.clientX-br.left);
var mousey = parseInt(e.clientY-br.top);
I have changed clientX and clientY to use pageX and pageY as others have suggested but the issue still remains, even if I place the canvas in another div and position accordingly. I've also subtracted the amount I'm moving the container over from mousex but without success. Even positioning with flex breaks the functionality.
Is this simply a limitation of canvas?
** Clarification, if it wasn't clear before, as detecting the mouse click is NOT the issue, the issue is when I position the canvas inside the DOM, the clicks are no longer registered. **
You can do it using offsetLeft and offsetTop. Here is a demo:
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
targetPos = {x: 150, y: 50};
// Initial render
render();
// Whenever the user clicks on the canvas, update the position
canvas.addEventListener('click', updateTargetPos);
function updateTargetPos(e) {
targetPos = {
x: e.pageX - canvas.offsetLeft,
y: e.pageY - canvas.offsetTop
};
render();
}
function render() {
renderBackground();
renderTarget();
}
function renderBackground() {
ctx.fillStyle = "#333";
ctx.fillRect(0, 0, 300, 200);
}
function renderTarget() {
var size = 10;
ctx.fillStyle = "#f00";
ctx.fillRect(targetPos.x - size/2, targetPos.y - size/2, size, size);
}
body {
font-family: Arial, Helvetica, sans-serif;
}
#canvas {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -150px;
}
<p>Click on this absolutely positioned canvas:</p>
<canvas id="canvas" width="300" height="100"></canvas>
I was editing this in devtools on the fly which was causing issues with canvas and being able to get the correct x/y of the mouse click. When I would adjust positioning in dev tools then attempt to move the square it would not work. Modifying my stylesheet prior to loading into the dom in this instance yielded the correct result as the calculations would need to be run again after manipulating the canvas positioning.
For those that needed a visual, here is some code. To replicate the problem you can try to add justify-content: center to the .flex class then attempt to move the green square to see that you can't move the square, or rather the spot that is defined to be clickable is not located over the box.
var canvas = document.getElementById("canvasArea");
var ctx = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var ctx2 = canvas2.getContext("2d");
var x = canvas.width/2;
var y = canvas.height-10;
var dx = 2;
var dy = -2;
var boundingRectangle = canvas.getBoundingClientRect();
var moveBox = false;
var selectedBox;
function box(x, y) {
this.color = "green";
this.xPos = x;
this.yPos = y;
this.width = 50;
this.height = 50;
}
box.prototype.drawBox = function() {
ctx.fillStyle = this.color;
ctx.fillRect(this.xPos, this.yPos, this.width, this.height);
};
var abox = new box(x, 30);
canvas.addEventListener("mousedown", function(e) {
e.preventDefault();
e.stopPropagation();
var mousex = parseInt(e.clientX-boundingRectangle.left);
var mousey = parseInt(e.clientY-boundingRectangle.top);
if(mousex > abox.xPos && mousex < abox.xPos + abox.width && mousey > abox.yPos && mousey < abox.yPos + abox.height) {
moveBox = true;
selectedBox = "abox"
}
}, true);
canvas.addEventListener("mousemove", function(e) {
e.preventDefault();
e.stopPropagation();
if(moveBox) {
if(selectedBox == "abox") {
abox.xPos = e.offsetX;
abox.yPos = e.offsetY;
}
}
})
canvas.addEventListener("mouseup", function(e) {
e.preventDefault();
e.stopPropagation();
moveBox = false;
})
function bg(ctx2) {
var alpha = ctx2.globalAlpha;
ctx2.save();
ctx2.beginPath();
ctx2.moveTo(142.5, 23.7);
ctx2.lineTo(85.9, 0.0);
ctx2.lineTo(0.0, 204.8);
ctx2.lineTo(56.6, 228.5);
ctx2.lineTo(142.5, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(31, 155, 215)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(235.1, 23.7);
ctx2.lineTo(178.5, 0.0);
ctx2.lineTo(92.6, 204.8);
ctx2.lineTo(149.2, 228.5);
ctx2.lineTo(235.1, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(77, 75, 159)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(330.5, 23.7);
ctx2.lineTo(273.9, 0.0);
ctx2.lineTo(188.0, 204.8);
ctx2.lineTo(244.6, 228.5);
ctx2.lineTo(330.5, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(176, 67, 152)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(435.4, 23.7);
ctx2.lineTo(378.8, 0.0);
ctx2.lineTo(292.9, 204.8);
ctx2.lineTo(349.5, 228.5);
ctx2.lineTo(435.4, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(69, 174, 77)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(541.4, 23.7);
ctx2.lineTo(484.7, 0.0);
ctx2.lineTo(398.9, 204.8);
ctx2.lineTo(455.5, 228.5);
ctx2.lineTo(541.4, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(237, 127, 34)";
ctx2.fill();
ctx2.restore();
}
function render() {
requestAnimationFrame(render);
ctx.clearRect(0, 0, canvas.width, canvas.height);
abox.drawBox();
}
render();
bg(ctx2)
.flex {
width: 100%;
height: 100%;
display: flex;
}
canvas#canvasArea {
position: absolute;
display: inline-block;
}
.container {
position: relative;
left: 150px;
top: -20px;
z-index: 999999;
}
.container {
position: relative;
}
.canvasbg {
position: absolute;
width: 200px;
height: 150px;
margin: 20px 0;
border: none;
background: #000000;
}
<div class="flex">
<div class="container">
<div class="canvasbg">
<canvas id="canvasArea" width="220" height="150">
Your browser does not support HTML5 canvas.
</canvas>
</div>
</div>
<canvas id="canvas2" width="540" height="177"></canvas>
I was following this MDN tutorial:
https://developer.mozilla.org/es/docs/Games/Workflows/Famoso_juego_2D_usando_JavaScript_puro/Mueve_la_bola
And I was wondering how could we control the ball's movement using mouse movement event.
I have seen that mousemove works with textarea and some inputs:
https://developer.mozilla.org/en-US/docs/Web/Events/mousemove
I thought if we could put a textarea behind the canvas, we could detect the event, get the mouse's position and let the user change the ball's movement with mouse movement.
I have read:
Dynamically resize textarea width and height to contain text
So I tried:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Juego</title>
<style>
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<textarea name="ta" id="ta" cols="30" rows="10"></textarea>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script>
const textarea = document.getElementById('ta');
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
textarea.style.height = canvas.height;
textarea.style.width = canvas.width;
let x = canvas.width / 2;
let y = canvas.height - 30;
const dx = 2;
const dy = -2;
let drawBall = function () {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
};
function draw(e) {
console.log(e);
ctx.clearRect(0,0,canvas.width,canvas.height);
drawBall();
x += dx;
y += dy;
}
setInterval(draw, 10);
canvas.addEventListener('mousemove', draw);
</script>
</body>
</html>
The expected output would be to have the textarea using canvas' width and height, and being behind it; however it is smaller and put on top left:
Thank you for your help.
you don't need the textarea to capture the event, and it'll be complicated because the canvas will be on top and the textarea will never know the mouse is moving on top of it, and to make the ball moving with the mouse, you have to passe the x and y of the mouse to the draw(); while it's moving function
here's a fiddle : https://jsfiddle.net/vqdyLx5u/22/
let canvas = document.getElementById('myCanvas');
let ctx = canvas.getContext('2d');
function drawBall(x, y) {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw(x, y) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall(x, y);
}
draw(canvas.height / 2 , canvas.width / 2); // initial draw
canvas.addEventListener('mousemove', function(e) {
draw(e.pageX, e.pageY); // the coordinates of the mouse
});
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
<canvas id="myCanvas" width="480" height="320"></canvas>
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