fabric.js Group members' properties don't update? - javascript

I'm using fabric.js to create objects on a canvas. I have a couple of objects together in a Group. When the group is selected, an event is triggered, which creates and adds a circle, centered at the end of a line. When a selection is cleared, another event is triggered, which removes the circle.
What I don't understand is why the circle doesn't change position to the line's new end point, when I move the group, deselect it, and then re-select it.
<html>
<head>
<script type="text/javascript" src="path/to/fabric.1.4.5.js"></script>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<script type="text/javascript">
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
var canvas = new fabric.Canvas('c');
var circle;
var text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'left',
originY: 'top',
left: 25,
top: 30
});
var line = new fabric.Line([10,10, 100,100], {
stroke: 'green',
strokeWidth: 2
});
var group = new fabric.Group([line, text], {
});
group.on('selected', function() {
circle = new fabric.Circle({
strokeWidth: 5,
radius: 28,
fill: 'rgba(200,200,200,0.4)',
stroke: '#666',
left: line.get('x2'),
top: line.get('y2')
})
canvas.add(circle);
});
canvas.add(group);
canvas.renderAll();
canvas.on('selection:cleared', function () {
group._objects.length = 2;
canvas.remove(circle);
});
</script>
</body>
</html>
Here's the same code on jsFiddle: http://jsfiddle.net/Tqmdw/

The position of your line actually doesn't change. Have a look at the groups tutorial. http://fabricjs.com/fabric-intro-part-3/#groups. In a group items are positioned relative to the groups center and the group is handled as a single entity. That means moving the group will only update the groups top and left position. You can use these new top/left values and calculate the endpoint of the line.
group.on('selected', function() {
circle = new fabric.Circle({
strokeWidth: 5,
radius: 28,
fill: 'rgba(200,200,200,0.4)',
stroke: '#666',
left: group.left + line.get('x2'),,
top: group.top + line.get('y2'),
})
canvas.add(circle);
});
I created an updated fiddle: http://jsfiddle.net/2e9B9/5/

Related

Fabricjs: How to get the area?

I need to know the area of all elements of the group. Is there a ready-made solution? Perhaps you know the area excluding the imposition?
window.canvas = new fabric.Canvas('fabriccanvas');
var circle1 = new fabric.Circle({
radius: 50,
fill: 'red',
left: 0
});
var Rect = new fabric.Rect({
left: 50,
top: 50,
height: 20,
width: 20,
fill: 'green'
});
var circle2 = new fabric.Circle({
radius: 20,
fill: 'black',
left: 70,
top: 80
});
var group = new fabric.Group([ circle1, circle2, Rect ], {});
window.canvas.add(group);
VINET,
There is no predefined functionality, you have to build your own. This is short example how to calculate area for circles and rectangles in the group (as you created above):
var groupArea = 0;
group.forEachObject(function(o){
groupArea += calculateArea(o);
});
function calculateArea(obj){
switch (obj.type){
case 'circle':
return Math.PI*obj.radius*obj.radius;
break;
case 'rect':
return obj.width*obj.height;
break;
}
}
alert(groupArea);
This post is how to calculate polygon area: Calculating Polygon Area
This post is for triangle area: Calculate triangle area and circumference from user input sides
Hopefully it will help you.

Canvas - Elements overlaying

I need to build some canvas app, but the shapes are irregular and elements are overlapping each other. I am using fabric.js for canvas and importing SVG files to draw elements, but I can't detect right hovered objects.
Here are examples:
I want to detect on mouse over when it will be above shape.
How it is actualy on my canvas (the red line corners are invisible in canvas ofc)
Example code from Fabric.js
You have to use the "perPixelTargetFind" property of fabricjs.
This will check the mouse over the object with accuracy.
If there is graphic it will trigger the target, otherwise not.
var canvas = new fabric.Canvas('canvas');
canvas.perPixelTargetFind = true;
canvas.add(new fabric.Circle({ radius: 30, fill: 'green', top: 50, left: 100 }));
canvas.add(new fabric.Circle({ radius: 30, fill: 'green', top: 100, left: 200 }));
canvas.on('mouse:over', function(e) {
e.target.setFill('red');
canvas.renderAll();
});
canvas.on('mouse:out', function(e) {
e.target.setFill('green');
canvas.renderAll();
});
<script src="http://fabricjs.com/lib/fabric.js"></script>
<canvas id='canvas' width="550" height="550" style="border:#000 1px solid;"></canvas>

kineticjs group cache and layer draw is hiding kinetic arc shapes

I am creating a circle as a group of kinetic arcs. When I cache the group and subsequently call the draw function on the layer, three quarters of the circle are hidden. I think layer.draw may require an offset but really I'm only guessing. When I remove the fill, stroke or opacity from the arc or the object literal from the cache call then the full circle is displayed. http://jsfiddle.net/leydar/gm2FT/5/ Any insights gratefully received.
function createArc(n){
var arc = new Kinetic.Arc({
innerRadius: 30,
outerRadius: 50,
/* if I remove the fill, stroke or opacity
the full wheel is correctly displayed */
fill: 'blue',
stroke: 'black',
opacity: 0.3,
strokeWidth: 1,
angle: 36,
rotation: 36*n
});
return arc;
}
function init() {
var arc;
var stage = new Kinetic.Stage({
container: 'container',
width: 104,
height: 104
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Group();
for(var i=0;i<10;i++) {
arc = createArc(i);
circle.add(arc);
};
layer.add(circle);
stage.add(layer);
/* if I do not cache or do not call layer.draw()
then again the wheel is correctly displayed */
circle.cache({
x: -52,
y: -52,
width: 104,
height: 104,
drawBorder: true
});
layer.draw();
}
init();
Stephen
This is a bug of KineticJS.
You may use this workaround:
Kinetic.Arc.prototype._useBufferCanvas = function() {
return false;
};
http://jsfiddle.net/gm2FT/6/

Kinetic JS Dragbound function limit drag on line

If I have a kinetic line
for example:
line = new Kinetic.Line({
x: 80,
y:80,
points: [10, 10, 60, 60, 60-headlen*Math.cos(angle-Math.PI/6),60-headlen*Math.sin(angle-Math.PI/6),60, 60, 60-headlen*Math.cos(angle+Math.PI/6),60-headlen*Math.sin(angle+Math.PI/6)],
stroke: "green",
// draggable: true,
strokeWidth: 4,
name: "letter_arrow",
offset: [140,140]
});
and say some circles
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: 'red',
fill: 'white',
strokeWidth: 2,
radius: 8,
name: name,
draggable: false,
dragOnTop: false,
//TODO SET THIS TO 0!!!!!
opacity: 1
});
where x and y will be the first 2 points of the line (10,10) and (60,60)
how can i make the circles drag bound, so that they can only be dragged on a imaginal line made through the first two points (10,10) and (60,60) i read the tutorials about drag bounds but i have no clue maybe you can help me
regards
You can add a custom dragBoundFunc to limit your anchors movement to a line:
// dragBoundFunc will constrain your shape to your desired x/y
// you receive the current mouse position in pos.x and pos.y
// you return the x/y where you want the shape to be located
dragBoundFunc: function(pos) {
// get the mouse x position
var x=pos.x;
// if the mouse is left or right of the line
// force the anchor to the far-left or far-right of the line
if(x<lineMinX){ return({ x:lineMinX, y:lineMinY}); }
if(x>lineMaxX){ return({ x:lineMaxX, y:lineMaxY}); }
// if the mouse between the left and right points of the line
// calc the desired y-position based on the formula: y = lineSlope * x + lineB
return({x:x,y:lineSlope*x+lineB});
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/UHwW9/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.2.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:300px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 300,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
var x1=10;
var y1=10;
var x2=60;
var y2=60;
var line=new Kinetic.Line({
points:[x1,y1,x2,y2],
stroke:"gray"
});
layer.add(line);
var anchor1=newAnchor(x1,y1,x2,y2);
function newAnchor(x1,y1,x2,y2){
var anchor = new Kinetic.Circle({
x: x1,
y: y1,
radius: 8,
fill: 'skyblue',
stroke: 'lightgray',
strokeWidth: 3,
draggable:true,
dragBoundFunc: function(pos) {
var x=pos.x;
if(x<this.minX){
return({x:this.minX,y:this.minY});
}
if(x>this.maxX){
return({x:this.maxX,y:this.maxY});
}
return({x:x,y:this.slope*x+this.b});
}
});
// dragBoundFunc stuff
anchor.minX=x1;
anchor.minY=y1;
anchor.maxX=x2;
anchor.maxY=y2;
anchor.slope=(y1-y2)/(x1-x2);
anchor.b=y1/(anchor.slope*x1);
//
layer.add(anchor);
layer.draw();
return(anchor);
}
}); // end $(function(){});
</script>
</head>
<body>
<button id="rotateBtn">rotate</button>
<div id="container"></div>
</body>
</html>
In MarkE's code, I believe the way to calculate anchor.b should be
anchor.b = y1 - anchor.slope * x1;
instead of
anchor.b = y1 / (anchor.slope * x1);
The formula is y = ax + b.
That could cause the bad behavior. http://jsfiddle.net/UHwW9/26/

KineticJS / Javascript dynamic creation and immediate dragging

What I want do is dynamically create a shape on mousedown, and then immediately have it (the shape) follow the mouse cursor until mouseup sets it in place.
Here is what I have so far and it's not working for me.
addNegativeButton.on('mousedown', function(){
var userPos = stage.getUserPosition();
shapesLayer.add(new Kinetic.Image({
image: imageObj,
x: userPos.x,
y: userPos.y,
height: 25,
width: 25,
rotation: 1,
draggable: true,
offset: 12
}));
var last = shapesLayer.getChildren()[shapesLayer.getChildren().length -1];
stage.on("mouseup", function(){
last.setAbsolutePosition(stage.getUserPosition());
stage.off("mouseup");
});
To reiterate:
What I have is an 'addNegativeButton' which when clicked creates a shape.
But I want it to follow the mouse cursor until the click is released.
http://jsfiddle.net/CPrEe/37/
Any suggestions?
Turns out its rather simple ;)
After you add the element/shape, all you have to do is use the simulate function to simulate mouse down and it drags....
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 20,
y: 20,
width: 100,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
//What I really want here is to start dragging the circle until the
//user releases the mouse, which would then place the circle.
rect.on('mousedown', function(evt) {
var userPos = stage.getUserPosition();
var latestElement = new Kinetic.Circle({
x: userPos.x,
y: userPos.y,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
})
layer.add(latestElement);
// Here's the bit you want. After adding the circle (with draggable:true) simulate the mousedown event on it which will initiate the drag
latestElement.simulate('mousedown');
layer.draw();
});
layer.add(rect);
stage.add(layer);​
http://jsfiddle.net/CPrEe/38/
http://kineticjs.com/docs/symbols/Kinetic.Node.php#simulate

Categories

Resources