Let users draw rectangles with mouse in canvas with Javascript - javascript

I am going to create a canvas to let users can draw some rectangles in canvas. It can show the rectangle when the user is dragging the mouse. Also, it allows the user to draw one or more rectangles in canvas. I've found a solution like this:
// get references to the canvas and context
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// style the context
ctx.strokeStyle = "blue";
ctx.lineWidth = 3;
// calculate where the canvas is on the window
// (used to help calculate mouseX/mouseY)
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// this flage is true when the user is dragging the mouse
var isDown = false;
// these vars will hold the starting mouse position
var startX;
var startY;
function handleMouseDown(e) {
e.preventDefault();
e.stopPropagation();
// save the starting x/y of the rectangle
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
// set a flag indicating the drag has begun
isDown = true;
}
function handleMouseUp(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
}
function handleMouseOut(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
}
function handleMouseMove(e) {
e.preventDefault();
e.stopPropagation();
// if we're not dragging, just return
if (!isDown) {
return;
}
// get the current mouse position
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// calculate the rectangle width/height based
// on starting vs current mouse position
var width = mouseX - startX;
var height = mouseY - startY;
// draw a new rect from the start position
// to the current mouse position
ctx.strokeRect(startX, startY, width, height);
}
// listen for mouse events
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function (e) {
handleMouseOut(e);
});
http://jsfiddle.net/m1erickson/6E2yd/
However, the solution can only allow users to draw one rectangle. If users draw the second rectangle, the previous rectangle will be wiped out because it will call ctx.clearRect() every time during dragging the mouse.

Edit: Sorry for my mistake. I missed the position:relative property for the container. Now it should work.
The code in your jsfiddle redraw repeatedly to give indications of the rectangle. I think it would be better to separate this indication canvas to a new layer, and use an overlapped layer to show the drawn rectangles.
JS:
// get references to the canvas and context
var canvas = document.getElementById("canvas");
var overlay = document.getElementById("overlay");
var ctx = canvas.getContext("2d");
var ctxo = overlay.getContext("2d");
// style the context
ctx.strokeStyle = "blue";
ctx.lineWidth = 3;
ctxo.strokeStyle = "blue";
ctxo.lineWidth = 3;
// calculate where the canvas is on the window
// (used to help calculate mouseX/mouseY)
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// this flage is true when the user is dragging the mouse
var isDown = false;
// these vars will hold the starting mouse position
var startX;
var startY;
var prevStartX = 0;
var prevStartY = 0;
var prevWidth = 0;
var prevHeight = 0;
function handleMouseDown(e) {
e.preventDefault();
e.stopPropagation();
// save the starting x/y of the rectangle
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
// set a flag indicating the drag has begun
isDown = true;
}
function handleMouseUp(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
ctxo.strokeRect(prevStartX, prevStartY, prevWidth, prevHeight);
}
function handleMouseOut(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
}
function handleMouseMove(e) {
e.preventDefault();
e.stopPropagation();
// if we're not dragging, just return
if (!isDown) {
return;
}
// get the current mouse position
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
// calculate the rectangle width/height based
// on starting vs current mouse position
var width = mouseX - startX;
var height = mouseY - startY;
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw a new rect from the start position
// to the current mouse position
ctx.strokeRect(startX, startY, width, height);
prevStartX = startX;
prevStartY = startY;
prevWidth = width;
prevHeight = height;
}
// listen for mouse events
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function (e) {
handleMouseOut(e);
});
HTML:
<h4>Drag the mouse to create a rectangle</h4>
<div id = "canvasWrapper">
<canvas id="overlay" width=300 height=300></canvas>
<canvas id="canvas" width=300 height=300></canvas>
</div>
CSS:
body{ background-color: ivory; }
canvas{
border: 1px solid red;
position: absolute;
}
#canvasWrapper{
position:relative;
}
http://jsfiddle.net/xkmqz9ho/

I've never tried anything on canvas, but you could try adding ctx.save() to the mouseout function and replacing ctx.clear(...) with ctx.restore().
more information about this on: https://developer.mozilla.org/es/docs/Web/API/CanvasRenderingContext2D/save

Related

Getting Error "addEventListener is not a function"

I want to pick my color from the color picker and then draw with this color in my canvas.
I get the Error:
Uncaught TypeError: colors.addEventListener is not a function
at window.onload
I use the npm color picker.
I get the Error at the call of the function:
colors.addEventListener('click', function(event)
My html:
<div class="picker"
acp-color="#EFE9E7"
acp-palette="#f44336|#e91e63|#9c27b0|#673ab7|#3f51b5|#2196f3|#03a9f4|#00bcd4|
#009688|#4caf50|#8bc34a|#cddc39|#cddc39|#ffeb3b|#ffc107|#ff9800|#ff5722|#795548|#9e9e9e|#20707|#befe7ee|#9e9e9e|#9e9e5e|#9e4e9e">
</div>
<div class="right-block">
<canvas id="paint-canvas" width="640" height="400"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/a-color-picker#1.1.8/dist/acolorpicker.js"></script>
<script src="./paint.js"></script>
My js code
var colors="#f44336";
AColorPicker.from('.picker')
.on('change', (picker, color) => {
colors= color;
});
var canvas = document.getElementById("paint-canvas");
var context = canvas.getContext("2d");
var boundings = canvas.getBoundingClientRect();
window.onload = function () {
// Definitions
// Specifications
var mouseX = 0;
var mouseY = 0;
context.strokeStyle = 'black'; // initial brush color
context.lineWidth = 1; // initial brush width
var isDrawing = false;
loadInput.addEventListener('change', (event) => this.load(event));
// Handle Colors
colors.addEventListener('click', function(event) {
context.strokeStyle = event.target.value || 'black';
});
// Mouse Down Event
canvas.addEventListener('mousedown', function(event) {
setMouseCoordinates(event);
isDrawing = true;
// Start Drawing
context.beginPath();
context.moveTo(mouseX, mouseY);
});
// Mouse Move Event
canvas.addEventListener('mousemove', function(event) {
setMouseCoordinates(event);
if(isDrawing){
context.lineTo(mouseX, mouseY);
context.stroke();
}
});
// Mouse Up Event
canvas.addEventListener('mouseup', function(event) {
setMouseCoordinates(event);
isDrawing = false;
});
// Handle Mouse Coordinates
function setMouseCoordinates(event) {
mouseX = event.clientX - boundings.left;
mouseY = event.clientY - boundings.top;
}
};
You cannot add an event listener to a string.
var colors="#f44336";
colors.addEventListener('click', function(event) {
context.strokeStyle = event.target.value || 'black';
});
String does not have this method. Given the assumption it remains a string after the picker on change event.
The thing is that, you need to change your context.strokeColor when the event fires up on the color change when picked, So you should Create a reference of the AColorPciker element and use its event emitter when your document is loaded
Then you can easily change your strokeColor, I hope this works for you.
I am adding the edited script, which should work for you.
// Initializing the color picker reference
var colorPicker = AColorPicker.from('.picker');
var canvas = document.getElementById("paint-canvas");
var context = canvas.getContext("2d");
var boundings = canvas.getBoundingClientRect();
window.onload = function () {
// Definitions
// Specifications
var mouseX = 0;
var mouseY = 0;
context.strokeStyle = 'black'; // initial brush color
context.lineWidth = 1; // initial brush width
var isDrawing = false;
// Here I am using the refernce of AColorPicker to set the context Stroke color, when the change is emitted on the color change
colorPicker.on('change', (picker, color) => {
context.strokeStyle = color;
});
// Mouse Down Event
canvas.addEventListener('mousedown', function(event) {
setMouseCoordinates(event);
isDrawing = true;
// Start Drawing
context.beginPath();
context.moveTo(mouseX, mouseY);
});
// Mouse Move Event
canvas.addEventListener('mousemove', function(event) {
setMouseCoordinates(event);
if(isDrawing){
context.lineTo(mouseX, mouseY);
context.stroke();
}
});
// Mouse Up Event
canvas.addEventListener('mouseup', function(event) {
setMouseCoordinates(event);
isDrawing = false;
});
// Handle Mouse Coordinates
function setMouseCoordinates(event) {
mouseX = event.clientX - boundings.left;
mouseY = event.clientY - boundings.top;
}
};

How to draw rectangles onto a JavaScript video?

I am trying to allow users to upload their own video which will then be displayed into a canvas. From there on, they can draw rectangles onto the canvas (to annotate for object detection), and clear the rectangles when they want to. However, what I want is that when the users draw the rectangles and play the video, the rectangles will follow an object on the video. Also, when the user wants to re-draw the rectangles to reposition them, they will clear the rectangles on the canvas - but the previously-drawn rectangles will already be saved.
This is the current code I have:
function update(){
context.drawImage(video,0,0,1580,700);
requestAnimationFrame(update); // wait for the browser to be ready to present another animation fram.
}
function readyToPlayVideo(event){ // this is a referance to the video
// the video may not match the canvas size so find a scale to fit
videoContainer.scale = Math.min(
canvas.width / this.videoWidth,
canvas.height / this.videoHeight);
videoContainer.ready = true;
// the video can be played so hand it off to the display function
requestAnimationFrame(updateCanvas);
// add instruction
/*document.getElementById("playPause").textContent = "Click video to play/pause.";*/
document.querySelector(".mute").textContent = "Mute";
}
function updateCanvas(){
context.clearRect(0,0,canvas.width,canvas.height);
// only draw if loaded and ready
if(videoContainer !== undefined && videoContainer.ready){
// find the top left of the video on the canvas
video.muted = muted;
var scale = videoContainer.scale;
var vidH = videoContainer.video.videoHeight;
var vidW = videoContainer.video.videoWidth;
var top = canvas.height / 2 - (vidH /2 ) * scale;
var left = canvas.width / 2 - (vidW /2 ) * scale;
// now just draw the video the correct size
context.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
/*if(videoContainer.video.paused){ // if not playing show the paused screen
drawPayIcon();
}*/
}
// all done for display
// request the next frame in 1/60th of a second
requestAnimationFrame(updateCanvas);
}
var canvasOffset = $("#canvas2").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var isDrawing = false;
var startX;
var startY;
var mouseIsDown = true;
var mouseIsUp = true;
var startX;
var startY;
function handleMouseDown(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
console.log(mouseX,mouseY);
$("#downlog").html("Down: " + mouseX + " / " + mouseY);
// Put your mousedown stuff here
if (mouseIsDown) {
console.log('1');
canvas2.style.cursor="crosshair";
mouseIsDown=false;
mouseIsUp=false;
console.log(mouseIsDown);
} else {
handleMouseUp();
}
mouseIsDown=false;
mouseIsUp=true;
}
function handleMouseUp(e) {
mouseIsDown=false;
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
if (mouseIsUp) {
console.log('2');
context2.beginPath();
context2.rect(startX,startY,mouseX-startX,mouseY-startY);
context2.strokeStyle="limegreen";
context2.lineWidth=2;
context2.stroke();
canvas2.style.cursor="default";
mouseIsUp=true;
}
}
$("#canvas2").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas2").mouseup(function (e) {
handleMouseUp(e);
});
function clearcanvas()
{
var canvas2 = document.getElementById('canvas2'),
context2 = canvas2.getContext("2d");
context2.clearRect(0, 0, canvas2.width, canvas2.height);
}
Any help is appreciated, thank you!

HTML5: whiteboard dimension?

I'm creating a simple whiteboard using html5 canvas.
I need to give the canvas a width and height. This causes the whiteboard to not work properly.
If I remove the width and height from the canvas, it works fine!
This is my code:
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
// Fill Window Width and Height
myCanvas.width = window.innerWidth;
myCanvas.height = window.innerHeight;
// Set Background Color
ctx.fillStyle="#fff";
ctx.fillRect(0,0,myCanvas.width,myCanvas.height);
// Mouse Event Handlers
if(myCanvas){
var isDown = false;
var canvasX, canvasY;
ctx.lineWidth = 5;
$(myCanvas)
.mousedown(function(e){
isDown = true;
ctx.beginPath();
canvasX = e.pageX - myCanvas.offsetLeft;
canvasY = e.pageY - myCanvas.offsetTop;
ctx.moveTo(canvasX, canvasY);
})
.mousemove(function(e){
if(isDown !== false) {
canvasX = e.pageX - myCanvas.offsetLeft;
canvasY = e.pageY - myCanvas.offsetTop;
ctx.lineTo(canvasX, canvasY);
ctx.strokeStyle = "#000";
ctx.stroke();
}
})
.mouseup(function(e){
isDown = false;
ctx.closePath();
});
}
// Touch Events Handlers
draw = {
started: false,
start: function(evt) {
ctx.beginPath();
ctx.moveTo(
evt.touches[0].pageX,
evt.touches[0].pageY
);
this.started = true;
},
move: function(evt) {
if (this.started) {
ctx.lineTo(
evt.touches[0].pageX,
evt.touches[0].pageY
);
ctx.strokeStyle = "#000";
ctx.lineWidth = 5;
ctx.stroke();
}
},
end: function(evt) {
this.started = false;
}
};
// Touch Events
myCanvas.addEventListener('touchstart', draw.start, false);
myCanvas.addEventListener('touchend', draw.end, false);
myCanvas.addEventListener('touchmove', draw.move, false);
// Disable Page Move
document.body.addEventListener('touchmove',function(evt){
evt.preventDefault();
},false);
#myCanvas{
width:90%;
height:200px;
border:solid 1px #c8def0;
border-radius:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas">
Sorry, your browser does not support HTML5 canvas technology.
</canvas>
basically, when I run the above code, the mouse doesn't go all the way down the canvas but if I remove the canvas width and height, it works fine.
Could someone please advice on this issue?
Thanks in advance.

Resize and move rectangles drawn on canvas

I am allowing the user to draw rectangles on an image. At the same time , the user should be able to resize or move any of the rectangles at any point of time.
With some help, i have been able to draw the rectangles but i am unable to come up with resizing and moving part of it.
The rectangles that are being drawn do not overlap one another and the same has to be validated while resizing and moving too.
I am using javascript and jquery.
This is a demo of what i have done so far :
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasOffset = $("#canvas").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var startX;
var startY;
var isDown = false;
ctx.strokeStyle = "lightgray";
ctx.lineWidth = 3;
function handleMouseDown(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousedown stuff here
startX = mouseX;
startY = mouseY;
isDown = true;
}
function handleMouseUp(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
$("#uplog").html("Up: " + mouseX + " / " + mouseY);
// Put your mouseup stuff here
isDown = false;
}
function handleMouseMove(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
if (!isDown) {
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRectangle(mouseX, mouseY);
}
function drawRectangle(mouseX, mouseY) {
var width = mouseX - startX;
var height = mouseY - startY;
ctx.beginPath();
ctx.rect(startX, startY, width, height);
ctx.stroke();
}
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
as i am running short of time and i am not able to figure out how this can be done.
AFAK, HTML canvas element is simply an array of pixels.
Then drawing/moving/resizing rectangles is, simply again, to keep redrawing canvas.
So first, drawn objects need to be stored (maybe in array).
Second, corresponding mouse events are necessary.
Last, canvas redrawing is required.
Like:
var boxes = [];
var tmpBox = null;
document.getElementById("canvas").onmousedown = function(e) {...};
document.getElementById("canvas").onmouseup = function(e) {...};
document.getElementById("canvas").onmouseout = function(e) {...};
document.getElementById("canvas").onmousemove = function(e) {...};
Here is JSFiddle for demo: https://jsfiddle.net/wiany11/p7hxjmsj/14/
These 2 tutorials explain what you want:
http://simonsarris.com/blog/510-making-html5-canvas-useful
http://simonsarris.com/blog/225-canvas-selecting-resizing-shape
In short you should store the borders of the rectangles yourself and detect when the user clicks in the rectangle or on the border.
First you create an array to store your rectangles in
var rectangles = [];
Then you make a method to call every time you want to draw all your rectangles
function drawRectangles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < rectangles.length; i++) {
var rect = rectangles[i];
ctx.beginPath();
ctx.rect(rect.startX, rect.startY, rect.endX, rect.endY);
ctx.stroke();
ctx.closePath();
}
}
In your mouseUp you then push the rectangles you have created to the array
function handleMouseUp() {
...
// store the rectangle as an object in your array
var rectangle = {startX: startX, endX: mouseX, startY: startY, endY: mouseY};
rectangles.push(rectangle);
drawRectangles();
}
In your other handlers you can then detect if you click in a rectangle of when your mouse will move in one
You can't just draw objects onto the canvas if you want to move them. You need to create instances of your shape objects and manage those (hit-testing and rendering as required). It is not very complex, but requires a lot more code than you have so far.
Try this tutorial: http://simonsarris.com/blog/510-making-html5-canvas-useful

Html5-canvas- How to remove space between the point I click and the draw point

please see the image below for better understanding what I mean :
http://8pic.ir/images/c6kzf0n0eu1v81sv3lit.jpg
as you can see, I scrolled down and when I click on canvas to point a position , it gives space between the point I click and the point it draw the line .
this is my code:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw,ch;
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var nameimageis =1;
// set some canvas styles
ctx.strokeStyle='black';
// an array to hold user's click-points that define the clipping area
var points=[];
// load the image
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="http://localhost/image/D0D70A02A7_166.18K_{}_6458.JPG";
function start(){
// resize canvas to fit the img
cw=canvas.width=img.width;
ch=canvas.height=img.height;
// draw the image at 25% opacity
drawImage(1);
// listen for mousedown and button clicks
$('#canvas').mousedown(function(e){handleMouseDown(e);});
$('#reset').click(function(){ points.length=0; drawImage(1); });
}
rightclick = function(e) {
e.preventDefault();
if(!e.offsetX) {
e.offsetX = (e.pageX - $(e.target).offset().left);
e.offsetY = (e.pageY - $(e.target).offset().top);
}
var x = e.offsetX, y = e.offsetY;
for (var i = 0; i < points.length; i+=2) {
dis = Math.sqrt(Math.pow(x - points[i], 2) + Math.pow(y - points[i+1], 2));
if ( dis < 6 ) {
points.splice(i, 2);
draw();
record();
return false;
}
}
return false;
};
function handleMouseDown(e){
// tell the browser that we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate mouseX & mouseY
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
// push the clicked point to the points[] array
points.push({x:mx,y:my});
// show the user an outline of their current clipping path
outlineIt();
// if the user clicked back in the original circle
// then complete the clip
/*if(points.length>1){
var dx=mx-points[0].x;
var dy=my-points[0].y;
if(dx*dx+dy*dy<10*10){
clipIt();
}
}*/
}
// redraw the image at the specified opacity
function drawImage(alpha){
ctx.clearRect(0,0,cw,ch);
ctx.globalAlpha=alpha;
ctx.drawImage(img,0,0);
ctx.globalAlpha=1.00;
}
// show the current potential clipping path
function outlineIt(){
drawImage(1);
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=0;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(points[0].x,points[0].y,10,0,Math.PI*2);
ctx.closePath();
ctx.stroke();
}
How can I remove this space and point the right position every where I clicked?
It's because you are using e.clientY in function handleMouseDown. clientX/Y is relative to the viewport, not the document. Try use e.pageX/Y, which are relative to the document content.
More details in this question.

Categories