Reputation: 87
I need to draw with html5 canvas this shape, I'm able to draw it as one canvas.
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
Reputation:
You can embed the complex shape in an object which handle the position and checks. An object could look like this:
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
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