FabricJS: After deselect a group, the controllers appear rotated 180 degrees - javascript

After a deselect a group, if I selected a object, the controllers appears upside down (with a 180 degrees rotation).
Steps to reproduse the issue:
Select all objects
Deselect all objects
Select the triangle object (in the example of the code above)
(function() {
// Code from FabricJs tutorial - http://fabricjs.com/fabric-intro-part-1/#objects
var canvas = new fabric.Canvas('canvas');
// create a rectangle object
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 80,
height: 40
});
var triangle = new fabric.Triangle({
left: 300,
top: 100,
width: 40,
height: 40,
fill: 'blue',
flipX: true
});
canvas.add(rect);
canvas.add(triangle);
canvas.renderAll(); // this wasn't mentioned in the tutorial, but I'm pretty sure it's needed
})();
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.min.js"></script>
</head>
<body></body>
<h1>Flip issue after grouping objects</h1>
<h2>None of the objects have rotation. Please select both objects (create a group) and, then, deselect it. After this, select the triangle : triangle will have rotation</h2>
<canvas id="canvas" width="600" height="300"></canvas>
I already open a ticket on the github with this issue.
What I want to know, if there is any workaround that I could do to avoid this issue.
Note:
fabricJS 1.7.15 version.

Related

Adding transformer to dynamically generated shapes?

I have the button which add new group which have square, to layer when clicked very simple code I guess no need to post. But my question is that how can I add transformer to it when on clicked?, I have done it with this mouseleave and mouseenter functions.
group.on('mouseenter', () => {
transformer.borderEnabled(true);
transformer.resizeEnabled(true);
layer.draw();
});
group.on('mouseleave', () => {
transformer.borderEnabled(false);
transformer.resizeEnabled(false);
layer.draw();
});
It is in loop which creates new group named "group", It works fine but in circle when I hover it the transformer appears but then when I go to transformer's boxes to resize it consider as it is mouseleave but this is doing only in circle not in line text.
So can I have solution for active transformer on element which is clicked or for considering hover on transformer boxes as a hover on node? Thanks
The mouseleave() will always fire because the pointer must leave the group to use the transformer handles or spinner.
An alternative approach would be
click to enable the transformer,
leave the transformer in place even when the mouse moves away
wait for a click on some other shape to know you can hide the transformer.
That is the standard GUI approach I believe.
If you need to show hover focus then stick a transparent rectangle the size of the groups clientrect into the group and change its stroke from transparent to some colour in the mouseenter and back in the mouseleave. You will also maybe want to set the rect.listening to false so as it coes not interfere with mouse events on the shapes in the group, but then again it might help in dragging.
Demo below.
// Set up the canvas and shapes
let stage = new Konva.Stage({container: 'container1', width: 300, height: 200});
let layer = new Konva.Layer({draggable: false});
stage.add(layer);
// Add a transformer.
let transFormer1 = new Konva.Transformer();
layer.add(transFormer1);
// Create a sample group
let group1 = new Konva.Group();
layer.add(group1);
group1.add(new Konva.Circle({x: 20, y: 30, radius: 15, fill: 'magenta', stroke: 'black'}))
group1.add(new Konva.Circle({x: 60, y: 40, radius: 15, fill: 'magenta', stroke: 'black'}))
group1.add(new Konva.Rect({x: 90, y: 60, width: 25, height: 25, fill: 'magenta', stroke: 'black'}));
let pos = group1.getClientRect();
let boundRect1 = new Konva.Rect({name: 'boundRect', x: pos.x, y: pos.y, width: pos.width, height: pos.height, fill: 'transparent', stroke: 'transparent'});
group1.add(boundRect1);
// When mouse enters the group show a border
group1.on('mouseenter', function(){
let boundRect = this.find('.boundRect');
boundRect[0].stroke('red');
layer.draw();
})
// and remove border when mouse leaves
group1.on('mouseleave', function(){
let boundRect = this.find('.boundRect');
boundRect[0].stroke('transparent');
layer.draw();
})
// If the group is clicked, enable the transformer on that group.
group1.on('click', function(){
transFormer1.attachTo(this)
layer.batchDraw();
})
// For a more pleasing demo let us have 2 groups.
// Make a copy of group1, offset new group, and change fill on its child shapes except the bound rect
let group2 = group1.clone();
layer.add(group2)
group2.position({x: 120, y: 30});
for (let i = 0, shapes = group2.getChildren(); i < shapes.length; i = i + 1){
shapes[i].fill(shapes[i].fill() !== 'transparent' ? 'cyan' : 'transparent');
}
stage.draw();
<script src="https://unpkg.com/konva#^3/konva.min.js"></script>
<p>Move mouse over the shapes to see the group borders, click a group to apply the transformer.
</p>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
Got the answer!, I just create a public transformer and on stage click I am adding nodes to it no transformer to each group just one public transformer which hold one node at a time.

Event handler for object selection on fabricjs canvas

I have a canvas that has some elements and i would like to add an event handler when the user selects an element(clicks on an element) on the canvas.
I tried using selection: created and object: selected events to handle the same but It only works when a user selects an element and then clicks on somewhere else in the canvas and then tries to select another element it works perfectly, But When the user clicks on one element(event handler triggered) and then clicks on another element the event handler is not triggering. How do I handle this?
i see that there is an option to use as mentioned here in the issue with selection:created and
selection:updated https://github.com/fabricjs/fabric.js/issues/4886
But is there a way i could use them together ?
const canvas = new fabric.Canvas('paper', {
preserveObjectStacking: true,
enableRetinaScaling: false,
imageSmoothingEnabled: false
});
canvas.setHeight(500);
canvas.setWidth(700);
const red = new fabric.Rect({
id: 'red',
left: 10,
top: 10,
width: 100,
height: 100,
fill: 'red'
});
const green = new fabric.Rect({
id: 'green',
left: 150,
top: 150,
width: 100,
height: 100,
fill: 'green'
});
canvas.add(red);
canvas.add(green);
canvas.renderAll();
canvas.on("selection:created", function(obj){
alert(obj.target.id)
});
canvas.on("selection:updated", function(obj){
alert(obj.target.id)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>
Here is a pretty simple solution for having both the event handlers combined and linking both of them to the same method will be a simple solution for this, Any better solutions will be appreciated.
canvas.on({
'selection:updated': HandleElement,
'selection:created': HandleElement
});
function HandleElement(obj){
//Handle the object here
}

fabric js: objects grouping is not working correctly

I am having problem with grouping multiple objects on canvas. When I select objects using holding down shift key and then if I make a group of those objects, the newly added group has incorrect z-index of it's items. Pleas run the snippet bellow to understand the problem.
Please select green box first, then select blue and then red while the shift key is pressed. Then clik on Group button to see the problem.
canvas = new fabric.Canvas('canvas', {
isDrawingMode: false,
preserveObjectStacking: true
});
fabric.Object.prototype.objectCaching = true;
canvas.setWidth(380);
canvas.setHeight(310);
canvas.setBackgroundColor('#F2F2F2');
canvas.renderAll();
var topMost = new fabric.Rect({
left: 90,
top: 90,
width: 50,
height: 50,
fill: 'green',
});
var middle = new fabric.Rect({
left: 70,
top: 70,
width: 50,
height: 50,
fill: 'blue',
});
var bottom = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'red',
});
canvas.add(bottom);
canvas.add(middle);
canvas.add(topMost);
$(".group").on('click', function () {
var activegroup = canvas.getActiveGroup();
var objectsInGroup = activegroup.getObjects();
activegroup.clone(function (newgroup) {
canvas.discardActiveGroup();
objectsInGroup.forEach(function (object) {
canvas.remove(object);
});
canvas.add(newgroup);
});
});
h3{color: blue;}h5{color: red;}canvas{border: 2px solid black;}
button{padding: 8px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
<h3>Please select green box first, then select blue and then red while the shift key is pressed. Then clik on Group button to see the problem</h3>
<h5>Problem: Grouping multiple objects, selected using shift key down, changes their z-index after grouped</h5>
<button class="group">Group</button>
<canvas id="canvas"></canvas>
Fabric has never automatically preserved object order in groups. I see you were using preserveObjectStacking = true, and I think this might have caused your confusion. This property does not affect the actual order of the objects to "preserve" the way they are on the canvas. Instead, it changes the order the objects appear to have while selected, as well as the click-target-finding when clicking on these objects.
Objects in the active selection are added in the order of clicking, and adding with preserveObjectStacking, they get rendered in canvas order. The first 2 objects get ordered in stack order anyway.
In your specific case, you click
green -> activeObject
blue -> blue is under green so you get activeGroup with blue, green.
red -> red gets pushed in active group at end of objects array.
The order that you get when you clone is exactly blue, green, red.
To preserve the order that the objects have on the canvas, you can easily write a sort function and run it against your group's objects before cloning it.
var sorter = function (a, b) {
var idxA = canvas._objects.indexOf(a),
idxB = canvas._objects.indexOf(b);
return idxA > idxB ? 1 : 0;
};
var activegroup = canvas.getActiveGroup();
activegroup._objects.sort(sorter);
var objectsInGroup = activegroup.getObjects(); //these are now in the canvas's order
//.....continue with your original code

KineticJS: How can I include custom canvas shapes in a toDataURL function

My problem started with the version 5 of KineticJS, before that it was not a problem. Native KineticJS shapes such as squares and circles can be saved to an image file using the stage.toDataURL function. But it doesn't work for non-Kinetic shapes drawn with normal canvas methods such as beginPath(); and canvas.fill(); (version 4 did this fine). The following code draws two rectangles, one red and one blue. The red is custom, the blue is a native kinetic rectangle.
<body>
<div id="container"></div>
<button id="save">
Save as image
</button>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.2.min.js"> </script>
<script>
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var box = new Kinetic.Rect({
x: 400,
y: 80,
width: 100,
height: 50,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
layer.add(box);
stage.add(layer);
var canvas = layer.getCanvas().getContext('2d');
canvas.beginPath();
canvas.setAttr('strokeStyle', 'black');
canvas.setAttr('fillStyle', '#FF2222');
canvas.setAttr('lineWidth', 8);
canvas.rect(50,80,100,50);
canvas.stroke();
canvas.fill();
document.getElementById('save').addEventListener('click', function() {
stage.toDataURL({
callback: function(dataUrl) {
window.location.href = dataUrl;
}
});
}, false);
</script>
</body>
Both shapes appear, but only the blue rectangle appears in the image generated by the toDataURL function. The way they are drawn has changed in KineticJS 5, where you set attributes for fillStyle etc. so I'm thinking that may have something to do with it, or maybe the fact that the custom shape is added after the layer is added to the stage...
You are correct, between recent versions much has changed, and this has probably broken something in your drawing function.
You should consult the official docs on each item, but basically a custom shape has slightly updated properties... first of all "StrokeStyle" is no longer a valid property. Just use 'stroke'. Same thing with FillStyle.
Also -- 'dashArray' is no longer valid, now it's just 'dash' -- so I'm sure there are more things that changed that I'm not recalling... right, such as 'lineWidth' is now 'strokeWidth'...
Also -- the way you show or don't show strokes and fills has changed... yep, pretty much most of the way you used to do it has been changed slightly. 'drawFunc' is now 'sceneFunc' also...
var ctx = layer.getContext();
var customShape01 = new Kinetic.Shape({
sceneFunc: function(ctx) {
ctx.beginPath();
ctx.moveTo(162.1, 213.8);
ctx.bezierCurveTo(162.1, 213.8, 180.7, 215.3, 193.5, 214.5);
ctx.bezierCurveTo(205.8, 213.7, 221.8, 212.3, 222.8, 221.4);
ctx.bezierCurveTo(222.9, 221.7, 222.9, 222.0, 222.9, 222.3);
ctx.bezierCurveTo(222.9, 232.4, 204.6, 232.7, 192.0, 227.1);
ctx.bezierCurveTo(179.4, 221.5, 163.1, 213.8, 162.1, 213.8);
ctx.closePath();
ctx.fillStrokeShape(this);
},
id: 'customShape01',
fill: 'rgb(255, 0, 255)',
stroke: 'black',
strokeWidth: 2,
lineJoin: 'round',
dash: [5,5],
dashEnabled: 'true',
strokeEnabled: 'true'
});
check out a full working sample (you'll have to allow popups).
http://jsfiddle.net/axVXN/1/

KineticJS/Canvas makes safari crash on ipad when using multiple layers?

This is driving me crazy. If I run the simple HTML below it works on my IPad2 (A1396) but not on my IPad 3 (A1416). Whenever I rotate the IPad 3 from Portrait to Landscape Safari will crash. I looks like there is a relation between the number of layers added and the width/height (of each layer).
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.2.min.js"></script>
<script defer="defer">
var stage = new Kinetic.Stage({
container: 'container',
width: 580,
height: 400
});
var NrOfAttribute = 50;
for (var AttributeNo = 0, NrOfAttribute; AttributeNo < NrOfAttribute; AttributeNo++) {
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 20*AttributeNo,
y: 20*AttributeNo,
width: 50,
height: 10,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);
}
</script>
</body>
</html>
Is this a bug or some memory problem on the IPad? Any help would be appreciated because I really need this to work.
Thanks!
Edit: I just tested to do the same with "regular" Canvas and if I increase the number of layers it will also crash (so it doesn't seem to be Kinetic specific). It will also crash on Ipad2 if you increase the number of layers enough.
This might not be an option for you, but do you need a separate layer for each Rect, or could you put them all in the same layer? I suspect that would help reduce memory usage, as you'd wouldn't end up with 50 canvas elements. You could give each Rect a unique name and then still be able to manipulate them individually with something like rect = layer.find('.{name}'); # do something with rect

Categories

Resources