How to implement drag and drop for HTML5 Canvas painting application? - javascript

Based on Creating an HTML 5 canvas painting application I created a HTML5 canvas painting application. It works fine, but after creating each object I just need to drag the objects.
Working demo
How to implement drag and drop of the figures?

When the user clicks on the canvas, you have to check the coordinates (compare it to the coordinates for the objects), and see if it's on an object. E.g. You can test if a point (e.g. the coordinates for the mousedown even) is within a circle with this method:
function (pt) {
return Math.pow(pt.x - point.x,2) + Math.pow(pt.y - point.y,2) <
Math.pow(radius,2);
};
If the mousedown is on the object, you have to change the objects coordinates according to how the mouse is moved.
Here is an example, where you can drag a circle:
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = function() {
drawCircle(circle);
element = document.getElementById('canvas');
element.addEventListener('mousedown', startDragging, false);
element.addEventListener('mousemove', drag, false);
element.addEventListener('mouseup', stopDragging, false);
element.addEventListener('mouseout', stopDragging, false);
}
function mouseX(e) {
return e.clientX - element.offsetLeft;
}
function mouseY(e) {
return e.clientY - element.offsetTop;
}
var Point = function (x, y) {
this.x = x;
this.y = y;
return this;
}
var Circle = function (point, radius) {
this.point = point;
this.radius = radius;
this.isInside = function (pt) {
return Math.pow(pt.x - point.x, 2) + Math.pow(pt.y - point.y, 2) <
Math.pow(radius, 2);
};
return this;
}
function startDragging(e) {
var p = new Point(e.offsetX, e.offsetY);
if(circle.isInside(p)) {
deltaCenter = new Point(p.x - circle.point.x, p.y - circle.point.y);
}
}
function drag(e) {
if(deltaCenter != null) {
circle.point.x = (mouseX(e) - deltaCenter.x);
circle.point.y = (mouseY(e) - deltaCenter.y);
drawCircle(circle);
}
}
function stopDragging(e) {
deltaCenter = null;
}
function drawCircle(circle) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(circle.point.x, circle.point.y, circle.radius, 0, Math.PI*2, true);
ctx.fill();
}
var circle = new Circle(new Point(30, 40), 25);
var deltaCenter = null;
var element;
</script>
</head>
<body>
<canvas id="canvas" width="400" height="300"></canvas>
</body>
</html>
Try it on jsFiddle

The same effect can be accomplished using Raphael.js (http://raphaeljs.com/) with Joint.jS (http://www.jointjs.com/).
Shapes created with Raphael can be accessed like any DOM element and can be manipulated via attributes. It is an awesome framework.
Joint.js helps in connecting the shapes. They also have a diagramming library and can help create ERD, Statemachine and several common diagrams. The best part is that you can extend their diagram element and create your own custom elements. Its jaw-dropingly cool.
Checkout their demos with source code at http://www.jointjs.com/demos

If you are using the raphael as "raw" lib you must handle the undo/redo by yourself.
The graphiti lib did have Undo/Redo Stack inside and supports the export for SVG, PNG, JSON,...
Additional you have some kind of Viso like connectors and ports.
http://www.draw2d.org/graphiti/jsdoc/#!/example
Greetings

I don't think there's an easy way to do this.
If you're just dealing with lines, my approach would be to keep track of all lines created, with starting coordinates, ending coordinates and some kind of z-index. When the user starts a dragging action (onmousedown), you have to check if the point is near the line, and then update the object and redraw the canvas when the mouse is moved.
How can I tell if a point belongs to a certain line?
This gets a lot more complicated if you're dealing with complex objects though. You'll probably have to find a solution to check if a point is inside a path.

Objects drawn into HTML5 Canvas are turned into pixels and then forgotten. You can't adjust properties on them and have the canvas update to see the effects. You can remember them yourself, but the canvas will still have those pixels set, so you'd have to basically redraw the whole canvas (or at least some of it) when you adjust a property.
You might want to consider SVG for this application instead, SVG elements are remembered in the DOM and when their properties are updated the browser will update the graphic to reflect the changes.
If you must use canvas, then you're going to need to write quite a bit of code to handle mouse-hits, object properties, and repaints.

Related

How to optimize this Javascript code for a Paint App?

I'm working on a Paint App using Processing.js. Basically, when the mouse is dragged, mouseX and mouseY are saved in an array of objects called data[]. Afterwards the paint() function will run a loop that accesses every object of the data[] array and draws a line of color(data[i].R,data[i].G,data[i].B) and thickness data[i].T between the corresponding data[i].mouseX and data[i].mouseY coordinates. The problem is that the array keeps getting bigger the more you draw and in my case, when the length of the data[] array reaches ~800 elements it will start to lag, and keeps getting worse the more I keep drawing. Is there any tweak that will fix the lag or do I have to completely rethink the program?
<!DOCTYPE HTML>
<html>
<head>
<script src="https://github.com/downloads/processing-js/processing-js/processing-1.4.1.min.js"></script>
<script type="text/processing" data-processing-target="targetcanvas">
void setup() {
size(649, 600);
}
background(255,255,255);
var r=0;
var g=0;
var b=0;
var data = [];
var mousex;
var mousey;
var thickness=31;
var painting = false;
var counter=0;
var x;
var paint = function() {
background(255, 255, 255);
for(var i=1;i<data.length;i++){
if (data[i-1].mousex && data[i].mousex) {
strokeWeight(data[i].T);
stroke(data[i].R, data[i].G, data[i].B);
line(data[i].mousex,data[i].mousey,data[i-1].mousex,data[i-1].mousey);
fill(0,0,0);
text(data.length,10,10);
}
};
};
mouseDragged = function(){
painting = true;
data.push({mousex: mouseX, mousey: mouseY, R:r, G:g, B:b, T:thickness});
paint();
counter++;
};
mouseReleased = function() {
x=counter;
counter=0;
if(painting) {
data.push({mousex: 0, mousey: 0});
}
painting = false;
};
mouseOut = function() {
data.push({mousex: 0, mousey: 0});
}
</script>
<center>
<canvas id="targetcanvas"width="649" height="600" " style="border: 3px solid black; margin-top=100px;"></canvas>
</center>
</body>
</html>
Is this Processing.js or P5.js? Either way, the answer is the same.
You basically have a spectrum of options:
No data structures, no undo: Instead of storing your shapes and whatnot in data structures, just draw them directly to the canvas whenever the user does something. You can use a PGraphics canvas, or you can just draw directly to the screen, if you get rid of the call to background(). Then you don't need any data structures. The downside of this is you won't be able to remove shapes once they're drawn.
Some data structures, some undo: If you needed to be able to undo some of the shapes, but not all of them, you could do a mix of the above approach and your current approach. Instead of storing everything in data structures, you would only store the last 1-10 or so shapes. The rest of the shapes would be drawn directly to the PGraphics buffer.
Lots of data structure, lots of undo: If you really needed to be able to undo all of the shapes, then you could still use the PGraphics approach, but only redraw all of the shapes when something was removed.

How to use player own-made sprite as in-game sprite

I am seeking for a way that allows players to draw their own characters and later to be able to play with them in-game. To make the concept very simple lets make the drawn player just a 2d shape or a group of lines and circles or even a mix of both. I think this can be decomposed in these steps:
An empty canvas is made for the user to create what he wants:
By painting or something of that genre
By using a specific polygon creation interface that allows dragging and molding shapes;
Note: I was able to create a way to paint on canvas *** but I didn't find a way to let user drag and create shapes or something of that kind. I would more likely opt for the second option so some suggestions on how do this would be really appreciated.
window.onload = init;
function init() {
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var painting = document.getElementById('paint');
var paint_style = getComputedStyle(painting);
canvas.width = "1024"
canvas.height = "1024";
var mouse = {
x: 0,
y: 0
};
canvas.addEventListener('mousemove', function(e) {
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop;
}, false);
ctx.lineWidth = 3;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#00CC99';
canvas.addEventListener('mousedown', function(e) {
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y);
canvas.addEventListener('mousemove', onPaint, false);
}, false);
canvas.addEventListener('mouseup', function() {
canvas.removeEventListener('mousemove', onPaint, false);
}, false);
var onPaint = function() {
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tojo - canvas draw</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<script src="app.js"></script>
<div id="paint"></div>
<canvas id="myCanvas" style="border: 3px solid black;"></canvas>
</body>
</html>
(paint on canvas snippet) ***
Somehow, I need to only get the user drawn / shaped creation from all the canvas. There should be a way to "surround" what the user has created and then save it:
By (in the drawing case probably) checking only for the color comparison between the white and the player color;
By, somehow, defining different points that all together would countain the user creation (either drawn or shaped);
Note: In this case I am kind of lost. I have almost no clue of how this could be made. However, I have heard of box2d but I think it was more focused in C or something of that kind? Would it work and how could I do it?
I would finally have the user creation stored and ready to load it in game as an image of a sprite.
I hope I could explain it all correctly. Can someone give me some orientation on this? FYI I am using Phaser.js as js game engine. (sorry for any mistakes, I am not very experienced with this matters).
Hopefully this will get you headed in the right direction:
Instead of drawing to the canvas, create a Phaser bitmapData object and have the user draw to that object. See this example that shows how to draw to a bitmapData object.
If you follow #1, then all of the data that you want will be stored in the bitmapData object, and you can create a Sprite directly from your bitmapData object, e.g.:
// Create a bitmapData object for the user to draw on
var bmd = this.game.make.bitmapData(64, 64);
// code to draw on `bmd` goes here
// Create a Sprite based on the bitmapData from above
var userSprite = this.game.add.sprite(0, 0, bmd);

Move a HTML5 Canvas Element

I'm making a small jigsaw like puzzle in html5. Each jigsaw piece is it's own canvas. I need to move the canvas element using the mouse position. I've managed to get the canvas that is clicked, I just need to move it. I tried manipulating the top and left style attributes but the canvas didn't move. Can this be done or am I trying something impossible.
Thanks!
function MouseDown(can, e)
{
MovingCanvas = can;
clicked = true;
}
function MouseMove(e)
{
if(clicked)
{
var mx = e.clientX;
var my = e.clientY;
MovingCanvas.style.top = my;
MovingCanvas.style.left = mx;
}
}
e.clientX and e.clientY are integers.
Styles expect a string of the form {NUMBER}{UNIT}.
You are missing a unit, therefore it won't work.
MovingCanvas.style.top = my+"px";
MovingCanvas.style.left = mx+"px";

touchmove drawing two lines instead of one on canvas

I am making a PhoneGap application with jQuery Mobile UI framework. I need a page where users will be able to draw stuff on the screen. I used this for reference and it works great in Ripple Emulator. However, on my actual device, a Nexus 4, instead of one line per touchmove, I get two lines. Is there something wrong with what I am doing?
EDIT: I found a similar problem reported in github. It seems to be the problem with Android's browser. The two lines were due to overlapping canvas elements. The only solution is to have canvas size less than 256px. Here's the link:
https://github.com/jquery/jquery-mobile/issues/5107
Here's my code
// start canvas code
var canvas = null; //canvas object
var context = null; //canvas's context object
var clearBtn = null; //clear button object
var buttonDown = false;
function captureDraw(){
canvas = document.getElementById('canvas');
clearBtn = document.getElementById('clearBtn');
setCanvasDimension();
initializeEvents();
context = canvas.getContext('2d');
}
function setCanvasDimension() {
//canvas.width = 300;
// canvas.width = window.innerWidth;
// canvas.height = window.innerHeight; //setting the height of the canvas
}
function initializeEvents() {
canvas.addEventListener('touchstart', startPaint, false);
canvas.addEventListener('touchmove', continuePaint, false);
canvas.addEventListener('touchend', stopPaint, false);
clearBtn.addEventListener('touchend', clearCanvas,false);
}
function clearCanvas() {
context.clearRect(0,0,canvas.width,canvas.height);
}
function startPaint(evt) {
if(!buttonDown)
{
context.beginPath();
context.moveTo(evt.touches[0].pageX, evt.touches[0].pageY);
buttonDown = true;
}
evt.preventDefault();
}
function continuePaint(evt) {
if(buttonDown)
{
context.lineTo(evt.touches[0].pageX,evt.touches[0].pageY);
context.stroke();
}
}
function stopPaint() {
buttonDown = false;
}
// end canvas code
Thanks!
Not an actual answer, but I found out that this is a known bug since Android 4.1.1. There have been many solutions like overriding offset-x: visible to the parent div of the canvas element, but it didn't work for me. See https://code.google.com/p/android/issues/detail?id=35474 for more information.
Other solution is keeping your canvas size below 256px. This is certainly a weird bug!

javascript canvas detect click on shape

I have a problem with the click function in javascript. This is my code:
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext('2d');
BigCircle = function(x, y, color, circleSize) {
ctx.shadowBlur = 10;
ctx.shadowColor = color;
ctx.beginPath();
ctx.arc(x, y, circleSize, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
};
var bigGreen = new BigCircle(1580, 800, '#5eb62b', 180);
function init() {
$("#bigGreen").click(function(e){
alert("test");
});
}
$(document).ready(function() {
init();
});
But the click event is not working! Does anybody know why? Thank you so much in advance!
You can now use hit regions in Chrome and Firefox:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility#Hit_regions
(Edit: Hit regions are now obsolete)
or just use one of the many canvas APIs:
http://www.fabricjs.com/
http://www.createjs.com/easeljs
http://www.paperjs.org
etc...
without seeing your html this question is a little bit unclear, it seems you would like to draw something on a canvas and use jquery to add click events for the circle, this isn't possible.
you can use jquery to get the click event ON the canvas and from the cursor position you can calculate if the user clicked the circle or not, but jquery won't help you here you have to do the math yourself.
jquery does only work for dom elements.
BigCircle = function(ctx,x, y, color, circleSize) {
ctx.beginPath();
ctx.arc(x, y, circleSize, 0, Math.PI * 2, true);
ctx.fillStyle=color
ctx.fill();
ctx.closePath();
this.clicked=function(){
ctx.fillStyle='#ff0000'
ctx.fill();
}
};
function init() {
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext('2d');
var bigGreen = new BigCircle(ctx,50, 50, '#5eb62b', 50);
$('#canvas').click(function(e){
var x = e.clientX
, y = e.clientY
if(Math.pow(x-50,2)+Math.pow(y-50,2) < Math.pow(50,2))
bigGreen.clicked()
})
}
$(document).ready(function() {
init();
});
​
jsfiddle is here
http://jsfiddle.net/yXVrk/1/
Canvas API function isPointInPath() can be used to assist with hit detection. This function can at least tell if mouse coordinates are within a complex shape without doing sophisticated math yourself. May work for simple shapes as well but my use case was for on a bezier curve path. However, you need to either incorporate this function in your drawing logic to test while paths are open or keep an array of Path2d objects to test against. I redraw on onClick handler and pass in mouse coords from event args, but I think I could have kept an array of Path2d objects instead.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
bigGreen is not in the HTML, so $("#bigGreen") selects nothing. You can't put a click function on things like JavaScript functions; since they don't exist in the DOM, how could you click one? You should replace #bigGreen with #canvas, since "canvas" is your HTML element.
I forked your fiddle to show this here.
Edit: If you want to see that the user clicked on a particular circle, you use the canvas click event, and then, you determine which circle was clicked by the coordinates passed into the click event.

Categories

Resources