I want draw a fabric.Polygon
with mouse interaction in Fabric.js.
I made a small jsfiddle to show my actual state: http://jsfiddle.net/Kienz/ujefxh7w/
After pressing the ESC key, the "interactive draw mode" is canceled and the polygon is finalized. But now the position of the polygon is wrong (controls are right).
Has anyone an idea?
Problem definition
When the polygon is added as static object (in sense that the points won't be modified) it's left, right, minX, minY, width, height
and center point can be calculated based on provided points.
But when one wants a polygon to be created dynamically the problem arises.
After calling setCoords()
it calculates the locations of the move boxes but it's based on the incorrect information about width and height.
Remember, when creating 1 point Polygon, it has width and height equal to 0, and the top left is equal to that one point.
Solution
First, correct the size
_calcDimensions()
calculates width
, height
, minX
and minY
of the polygon. minX
and minY
are the minima in all points.
This informs us how far left and top above the old center some points were placed. We should use this information to move the old left to the correct spot. The new top-left point is the old center point translated by the minX
and minY
information.
var oldC = polygon.getCenterPoint();
polygon._calcDimensions();
polygon.set({
left: oldC.x + polygon.get('minX'),
top: oldC.y + polygon.get('minY')
});
Fix points
After the center has moved by some vector v
(a result of changing left
, right
and width
, height
properties). We need to updated all points by a reverse of 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
};
});
Check following Fiddle for the full 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();
};
There are two solutions to this problem, which are very very simple:
Solution #1
Just remove the polygon from the canvas and recreate it (using new fabric.Polygon(...)) on every added point!
Pros & Cons: Yes, you will get a slightly worse performance, because the canvas will be rerendered twice, but you will save yourself the trouble of recalculating the coordinates every time.
You can check this solution 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
});
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>
Solution #2
Recalculate the polygon dimensions every time, like it is done in the constructor of 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 & Cons: Better performance (probably noticeable on heavily loaded canvases), but you will have more code as you have to add it to both handlers.
You can check this 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>
you can fix the 'jumping' polygon by setting the originX and originY points 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 here:
http://jsfiddle.net/ujefxh7w/48/
I don't know why the selection box is shifted.
EDIT: found that reloading of the canvas object fixes the select box
serializing, and loading the serialized objects does the trick:
var json = JSON.stringify(canvas);
canvas.loadFromJSON(json, function() {
canvas.renderAll();
});
http://jsfiddle.net/ujefxh7w/50/