user2553832
user2553832

Reputation: 87

Trying to draw a pattern divided to a few parts with html5 canvas

I need to draw with html5 canvas this shape, I'm able to draw it as one canvas.

http://postimg.org/image/li5mpoot3/

I need to handle each part of as a unique object - listen to mouse over each part, determine on which of the parts the user is hovering his mouse on, and also let the user the option to fill the relevant part of the shape.

var canvas = document.getElementById('shape');
var context = canvas.getContext('2d'),
width = $('#shape').width(),
part_width = width / parts_num;

context.beginPath();

var start_x = 17,
end_x = 17 + part_width;
for (var i = 0; i < parts_num; i++) {
context.moveTo(start_x, 0); 
context.lineTo(end_x,0);
context.moveTo(start_x, 0);
context.bezierCurveTo(50+start_x, 30, 40+start_x, 45, 13+start_x, 89);
context.moveTo(13+start_x, 89);
context.bezierCurveTo(0+start_x, 110, 0+start_x, 126, 27+start_x, 174);
context.moveTo(28+start_x, 174);
context.lineTo(85+start_x,174);
start_x += part_width;//80 is x starting point
};
context.lineWidth = 5;
context.strokeStyle = "red";
context.stroke();

Upvotes: 4

Views: 365

Answers (2)

user1693593
user1693593

Reputation:

Solution using vanilla JavasScript and canvas

You can embed the complex shape in an object which handle the position and checks. An object could look like this:

Full working demo here

Extract:

Main object -

function wave(ctx, offset, width) {

    /// the check function which checks point x, y in last path
    this.isInPath = function(x, y) {
        getPath();
        return ctx.isPointInPath(x, y);
    }

    /// this render the object with the set fill color
    this.draw = function(color) {
        getPath();
        ctx.fillStyle = color;
        ctx.fill();
        ctx.lineWidth = 5;
        ctx.strokeStyle = "red";
        ctx.stroke();
    }

    /// common function to generate just the path.
    function getPath() {
        ctx.beginPath();
        ctx.moveTo(offset, 0);
        ctx.bezierCurveTo(50 + offset, 30, 40 + offset, 45, 13 + offset, 89);
        ctx.bezierCurveTo(offset, 110, offset, 126, 27 + offset, 174);
        ctx.lineTo(27 + offset + width, 174);
        ctx.bezierCurveTo(offset + width, 100, offset + width + 0, 126, 27 + offset + width, 65);
        ctx.bezierCurveTo(43 + offset + width, 40, 40 + offset + width, 25, offset + width, 0);
        ctx.closePath();
    }

    this.draw('white');
    return this;
}

Make sure the path is self-contained, meaning that it is a complete shape not depending on the neighbor parts (ie. not using the neighbor lines). That is basically all you need to do to handle complex shapes.

Render

To render simply do this:

for(;i < parts_num; i++) {
    parts.push( new wave(context, i * part_width, part_width) );
}

Check

As to check if a point is in the part you can do this:

/// check mouse move in this example
canvas.addEventListener('mousemove', hover, false);

/// the function to check parts
function hover(e) {

    ///get mouse coord relative to canvas
    var r = canvas.getBoundingClientRect();
    var x = e.clientX - r.left;
    var y = e.clientY - r.top;

    ///optimize so we only clear last selected part
    if (lastPart > -1) {
        parts[lastPart].draw('white');
        lastPart = -1;
    }    

    for(i = 0 ;i < parts_num; i++) {
        if (parts[i].isInPath(x, y) === true) {
            parts[i].draw('green');    
            lastPart = i;
            break; //don't need to check more
        }
    }
}

There is room for optimization here (re-use last path inside object, cache the shape as images, segment check range as so on) but shows that it isn't really that complex to handle this using vanilla canvas and JavaScript.

With this as a base you should be able to handle clicks and so forth yourselves.

(Note: I haven't been too accurate with the closing end on the bezier - I'll leave it to you to fine-adjust it if you should choose to use this solution..).

Upvotes: 3

loxxy
loxxy

Reputation: 13151

I would suggest using CreateJS. It would make your life whole lot easier while working with canvas.

Here is a Demo of how you could handle each shape as an object with CreateJS.

Click on individual shape to see how each one triggers it's own handler.

function curveShape(index, start_x, end_x) {
    var shape = new createjs.Shape();
    var context = shape.graphics.beginStroke("red").beginFill("#ffffee");    
    context.moveTo(start_x, 0);
    context.bezierCurveTo(start_x + 10, 30, start_x + 10, 45, start_x - 15, 90);
    context.bezierCurveTo(start_x - 30, 120, start_x - 30, 135, start_x, 180);
    context.lineTo(end_x, 180);
    context.bezierCurveTo(end_x - 30, 135, end_x - 30, 120, end_x - 15, 90);
    context.bezierCurveTo(end_x + 10, 45, end_x + 10, 30, end_x, 0);
    context.lineTo(start_x, 0);

    shape.name = "shape " + index;
    shape.x = shape.y = 30;
    shape.addEventListener("mousedown", function () {
        alert("I am " + shape.name);
    });

    return shape;
}

var stage = new createjs.Stage("shape");    

for(var i = 0; i < 4; i++) 
    stage.addChild(new curveShape(i+1, i * 80, (i + 1) * 80));   

stage.update();

Upvotes: 3

Related Questions