HTML5 drag and drop path on canvas without JS libr

2019-05-14 02:29发布

问题:

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.

回答1:

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 )