Fabric.js: How to draw a polygon with the mouse
I want to draw a fabric.Polygon
with mouse interaction in Fabric.js. I made a little jsfiddle to show my actual state: http://jsfiddle.net/Kienz/ujefxh7w/
Pressing the ESC key cancels the "interactive drawing mode" and the polygon ends. But now the position of the polygon is wrong (controls are right).
Anyone have an idea?
source to share
Defining the problem
When a polygon is added as a static object (in the sense that the points will not change), it left, right, minX, minY, width, height
and the center point can be calculated based on the provided points.
But when you need to create a polygon dynamically, the problem arises. Once called, setCoords()
it calculates the locations of the move fields, but based on the incorrect width and height information.
Remember , when you create a 1-point polygon, it has a width and height of 0, and the top-left is that one point.
Decision
Correct the size first
_calcDimensions()
calculates width
, height
, minX
and minY
a polygon. minX
and minY
are minima at all points.
This tells us how far to the left and above the old center some points were placed. We need to use this information to move the old left to the correct location. The new top left point is the old center point translated with information minX
and minY
.
var oldC = polygon.getCenterPoint();
polygon._calcDimensions();
polygon.set({
left: oldC.x + polygon.get('minX'),
top: oldC.y + polygon.get('minY')
});
Fix points
After moving the center by some vector v
(the result of changing the properties left
, right
and width
, height
). We need to update all the points with the opposite value v
.
var newC = polygon.getCenterPoint();
var moveX = -(newC.x-oldC.x);
var moveY = -(newC.y-oldC.y)
var adjPoints = polygon.get("points").map(function(p) {
return {
x: p.x + moveX,
y: p.y + moveY
};
});
Complete the Fiddle for a complete example: http://jsfiddle.net/orian/dyxjkmes/5/
/**
* fabric.js template for bug reports
*
* Please update the name of the jsfiddle (see Fiddle Options).
* This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/all.js).
*/
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
// Do some initializing stuff
fabric.Object.prototype.set({
transparentCorners: false,
cornerColor: 'rgba(102,153,255,0.5)',
cornerSize: 12,
padding: 7
});
// ADD YOUR CODE HERE
var mode = "add",
currentShape;
canvas.observe("mouse:move", function(event) {
var pos = canvas.getPointer(event.e);
if (mode === "edit" && currentShape) {
var points = currentShape.get("points");
points[points.length - 1].x = pos.x - currentShape.get("left");
points[points.length - 1].y = pos.y - currentShape.get("top");
currentShape.set({
points: points
});
canvas.renderAll();
}
});
canvas.observe("mouse:down", function(event) {
var pos = canvas.getPointer(event.e);
if (mode === "add") {
var polygon = new fabric.Polygon([{
x: pos.x,
y: pos.y
}, {
x: pos.x + 0.5,
y: pos.y + 0.5
}], {
fill: 'blue',
opacity: 0.5,
selectable: false
});
currentShape = polygon;
canvas.add(currentShape);
mode = "edit";
} else if (mode === "edit" && currentShape && currentShape.type === "polygon") {
var points = currentShape.get("points");
points.push({
x: pos.x - currentShape.get("left"),
y: pos.y - currentShape.get("top")
});
currentShape.set({
points: points
});
canvas.renderAll();
}
});
fabric.util.addListener(window, 'keyup', function(e) {
if (e.keyCode === 27) {
if (mode === 'edit' || mode === 'add') {
mode = 'normal';
var points = currentShape.get("points");
points.pop();
currentShape.set({
points: points
});
var oldC = currentShape.getCenterPoint();
currentShape._calcDimensions();
var xx = currentShape.get("minX");
var yy = currentShape.get("minY");
currentShape.set({
left: currentShape.get('left') + xx,
top: currentShape.get('top') + yy
});
var pCenter = currentShape.getCenterPoint();
var adjPoints = currentShape.get("points").map(function(p) {
return {
x: p.x - pCenter.x + oldC.x,
y: p.y - pCenter.y + oldC.y
};
});
currentShape.set({
points: adjPoints,
selectable: true
});
canvas.setActiveObject(currentShape);
currentShape.setCoords();
canvas.renderAll();
} else {
mode = 'add';
}
currentShape = null;
}
})
canvas {
border: 1px solid #999;
}
img {
display: none;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Copy & paste
function fixPoly(poly) {
var oldC = poly.getCenterPoint();
poly._calcDimensions();
poly.set({
left: poly.get('left') + poly.get("minX"),
top: poly.get('top') + poly.get("minY")
});
var pCenter = poly.getCenterPoint();
poly.get("points").forEach((p) => {
p.x -= pCenter.x - oldC.x;
p.y -= pCenter.y - oldC.y
});
poly.setCoords();
};
source to share
There are two solutions to this problem, which are very simple:
Solution # 1
Just remove the polygon from the canvas and re-create it (using the new fabric.Polygon (...)) for each point added!
Pros and Cons: Yes, you will get a slightly worse performance because the canvas will be republished twice, but you will save time recalculating the coordinates each time.
You can check out this solution in the snippet below or in this forked fiddle .
/**
* fabric.js template for bug reports
*
* Please update the name of the jsfiddle (see Fiddle Options).
* This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/all.js).
*/
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
// Do some initializing stuff
fabric.Object.prototype.set({
transparentCorners: false,
cornerColor: 'rgba(102,153,255,0.5)',
cornerSize: 12,
padding: 7
});
// ADD YOUR CODE HERE
var mode = "add",
currentShape;
canvas.observe("mouse:move", function(event) {
var pos = canvas.getPointer(event.e);
if (mode === "edit" && currentShape) {
var points = currentShape.get("points");
points[points.length - 1].x = pos.x;
points[points.length - 1].y = pos.y;
currentShape.set({
points: points
});
canvas.renderAll();
}
});
canvas.observe("mouse:down", function(event) {
var pos = canvas.getPointer(event.e);
if (mode === "add") {
var polygon = new fabric.Polygon([{
x: pos.x,
y: pos.y
}, {
x: pos.x + 0.5,
y: pos.y + 0.5
}], {
fill: 'blue',
opacity: 0.5,
selectable: false
});
currentShape = polygon;
canvas.add(currentShape);
mode = "edit";
} else if (mode === "edit" && currentShape && currentShape.type === "polygon") {
var points = currentShape.get("points");
points.push({
x: pos.x,
y: pos.y
});
canvas.remove(currentShape);
currentShape = new fabric.Polygon(points, {
fill: 'blue',
opacity: 0.5,
selectable: false
});
canvas.add(currentShape);
}
});
fabric.util.addListener(window, 'keyup', function(e) {
if (e.keyCode === 27) {
if (mode === 'edit' || mode === 'add') {
mode = 'normal';
var points = currentShape.get('points');
canvas.remove(currentShape);
currentShape = new fabric.Polygon(points, {
fill: 'blue',
opacity: 0.5,
selectable: true
});
canvas.add(currentShape);
} else {
mode = 'add';
}
currentShape = null;
}
})
canvas {
border: 1px solid #999;
}
img {
display: none;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Decision # 2
recalculate the dimensions of the polygon each time you do it in the constructor of the Polygon class. Excerpt from the code:
currentShape.set({
points: points
});
currentShape._calcDimensions();
currentShape.set({
left: currentShape.minX,
top: currentShape.minY,
pathOffset: {
x: currentShape.minX + currentShape.width / 2,
y: currentShape.minY + currentShape.height / 2
}
});
currentShape.setCoords();
canvas.renderAll();
Pros and Cons: Better performance (probably noticeable on heavily loaded canvases), but you'll have more code since you have to add it to both handlers.
You can check it out in the snippet below or in this forked fiddle .
/**
* fabric.js template for bug reports
*
* Please update the name of the jsfiddle (see Fiddle Options).
* This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/all.js).
*/
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
// Do some initializing stuff
fabric.Object.prototype.set({
transparentCorners: false,
cornerColor: 'rgba(102,153,255,0.5)',
cornerSize: 12,
padding: 7
});
// ADD YOUR CODE HERE
var mode = "add",
currentShape;
canvas.observe("mouse:move", function(event) {
var pos = canvas.getPointer(event.e);
if (mode === "edit" && currentShape) {
var points = currentShape.get("points");
points[points.length - 1].x = pos.x;
points[points.length - 1].y = pos.y;
currentShape.set({
points: points
});
canvas.renderAll();
}
});
canvas.observe("mouse:down", function(event) {
var pos = canvas.getPointer(event.e);
if (mode === "add") {
var polygon = new fabric.Polygon([{
x: pos.x,
y: pos.y
}, {
x: pos.x + 0.5,
y: pos.y + 0.5
}], {
fill: 'blue',
opacity: 0.5,
selectable: false
});
currentShape = polygon;
canvas.add(currentShape);
mode = "edit";
} else if (mode === "edit" && currentShape && currentShape.type === "polygon") {
var points = currentShape.get("points");
points.push({
x: pos.x,
y: pos.y
});
currentShape.set({
points: points
});
currentShape._calcDimensions();
currentShape.set({
left: currentShape.minX,
top: currentShape.minY,
pathOffset: {
x: currentShape.minX + currentShape.width / 2,
y: currentShape.minY + currentShape.height / 2
}
});
currentShape.setCoords();
canvas.renderAll();
}
});
fabric.util.addListener(window, 'keyup', function(e) {
if (e.keyCode === 27) {
if (mode === 'edit' || mode === 'add') {
mode = 'normal';
currentShape.set({
selectable: true
});
currentShape._calcDimensions();
currentShape.set({
left: currentShape.minX,
top: currentShape.minY,
pathOffset: {
x: currentShape.minX + currentShape.width / 2,
y: currentShape.minY + currentShape.height / 2
}
});
currentShape.setCoords();
canvas.renderAll();
} else {
mode = 'add';
}
currentShape = null;
}
})
canvas {
border: 1px solid #999;
}
img {
display: none;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
source to share
you can fix the bouncing polygon by setting initialization points X and originY on initialization.
var polygon = new fabric.Polygon([{
x: pos.x,
y: pos.y
}, {
x: pos.x + 0.5,
y: pos.y + 0.5
}], {
fill: 'blue',
opacity: 0.5,
selectable: false,
originX: pos.x,
originY: pos.y
});
See it here: http://jsfiddle.net/ujefxh7w/48/
I don't know why the selection box is shifted.
EDIT: Found that reloading the canvas object fixes the select box
serializing and loading serialized objects does the trick:
var json = JSON.stringify(canvas);
canvas.loadFromJSON(json, function() {
canvas.renderAll();
});
source to share