I want to drag and drop path on HTML5 canvas.
I didn't find any thing like SVG provides events if we click on shape.
Anyone know how I can handle events on path item here? I want to do this without any java script library.
Following is my current code to draw path:
var canvas = document.getElementById("html5Canvas");
var context = canvas.getContext("2d");
var drawing = false;
canvas.addEventListener("mousedown", startDraw, false);
canvas.addEventListener("mousemove", continueDraw, false);
canvas.addEventListener("mouseup", endDraw, false
function startDraw(event)
{
drawing = true;
context.moveTo(event.clientX, event.clientY);
}
function continueDraw(event)
{
if (drawing)
{
context.lineTo(event.clientX, event.clientY);
context.stroke();
}
}
function endDraw(event)
{
if (drawing)
{
context.lineTo(event.clientX, event.clientY);
context.stroke();
drawing = false;
}
}
Thanks.
Preparations
In order to detect line-clicks we need to record all path information.
The following example is a modified version of the code provided in original post and includes a stroke recorder which records each stroke (between mouse down and mouse down) to an array which contains all strokes.
For simplicity we listen here to mouse clicks when we alter mode. When in click mode we iterate the stroke collection and re-build the paths we previously recorded for then to check if our mouse position is in one of these lines.
The code can be optimized by using a hit region before building paths to reduce overhead but for the sake of demo only the essential code is included:
Demo code
ONLINE DEMO HERE
To record we need to have something to store the lines/strokes in:
var lines = [], line;
We add an event listener for clicks that we'll use when we switch mode. Note that normally you would share mousedown/up events perhaps instead:
canvas.addEventListener("click", checkLine, false);
To make the lines more "clickable" we use a thicker line width here (fixed for demo):
context.lineWidth = 3;
In order to record we need to modify the existing callbacks. There is also a bug in it which causes the line to be redrawn of top of each other for each mouse move which eventually will slow down the drawing if the line is long.
We also need to adjust mouse positions so it becomes relative to canvas:
function startDraw(event) {
/// if we are in "click" mode exit from here (for demo)
if (mode.checked === true) return;
/// adjust mouse position
var pos = mouseXY(canvas, event);
drawing = true;
/// start a new path
context.beginPath();
context.moveTo(pos.x, pos.y);
/// create a new stroke and push first position to it
line = [];
line.push([pos.x, pos.y]);
}
For each part we draw we need to reset path so we don't redraw the whole line (in your final render/redraw you would of course just redraw the line in one go, but not while drawing it):
function continueDraw(event) {
if (drawing) {
/// adjust mouse position
var pos = mouseXY(canvas, event);
/// complete one line segment started in mouse down
context.lineTo(pos.x, pos.y);
context.stroke();
/// reset path and start from where we ended this line
context.beginPath();
context.moveTo(pos.x, pos.y);
/// store current point to stroke/line
line.push([pos.x, pos.y]);
}
}
And finally when line is finished we store our stroke:
function endDraw(event) {
if (drawing) {
var pos = mouseXY(canvas, event);
context.lineTo(pos.x, pos.y);
context.stroke();
drawing = false;
/// push stroke/line to line stack
lines.push(line);
}
}
We use this to adjust mouse position::
function mouseXY(c, e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top};
}
Checking line clicks
To check a line we need to iterate through our line collection and rebuild each line as a path. There is no need to draw these paths so the speed is OK. When a path is rebuilt we check the adjusted mouse position against the path by using isPointInStroke
:
function checkLine(e) {
if (mode.checked === false) return;
var i = 0, line, l, p, pos = mouseXY(canvas, e);
/// make sure stroke has same width as originally recorded
context.lineWidth = 3;
/// loop through line collection
for(; line = lines[i]; i++) {
/// reset path
context.beginPath();
/// begin stroke
context.moveTo(line[0][0], line[0][1]);
/// iterate through each point stored
for(l = 1; p = line[l]; l++) {
/// add a line
context.lineTo(p[0], p[1]);
}
/// then we check the point
if (context.isPointInStroke(pos.x, pos.y) === true) {
alert('hit line ' + i); /// show "ID" of line clicked
return;
}
}
}
Even complex overlapping lines can be detected with no problem:
(Yes I know! I could beat Dali and Munch any day! X-p )