Replace an object in FabricJS? - javascript

To implement a undo/redo functionality, I have constructed an array "history" that gets filled with the latest changes based on canvas.on() events.
console.log dump:
History:
(3) […]
​
0: Object { target: {…} } //first object added
​
1: Object { e: mouseup, target: {…}, transform: {…}, … } //first object modified
​
2: Object { target: {…} } //another object added
​
length: 3
To walk back the stack of changes, I wanted to use history[step].target which contains the modified object at this stage (step).
I now look for a method to overwrite an object in the object array of fabric.
The function canvas.item(0) gives the object at position 0 on the current canvas but how can I overwrite that object with a different object (from the history[].target array)?
Note: Solutions I found for a undo/redo are seemingly based on serializing the whole canvas into JSON and saving this into an array of JSONs. I didn't want to do this since it seems a bit unsophisticated to always serialize/unserialize the whole canvas with x objects in it just to undo a small change and the history contains many usefull informations about what was changed and on which object ect.
Note2: Of course I probably could just canvas.remove(canvas.item(0)) and then canvas.add(history[x].target), however this unfortunately messes up my object stack when there's more than one objects, as canvas.item(1) becomes canvas.item(0) and the reverted change now becomes canvas.item(1) (or 2, 3... ect depending on how many items on the canvas). When I then have like a list of objects displayed depending on their position in the object stack, this get's rearranged and could confuse the user.

This is a simple example.
Given 2 rect on on a canvas, a function will add one back in the middle of those 2, and another will replace the one at position 0.
canvas#insertAt does work as suggested from #gabriele-petroli
var c = new fabric.Canvas('c');
c.add(new fabric.Rect({ fill: 'blue', width: 100, height: 100 }));
c.add(new fabric.Rect({ fill: 'green', left: 150, width: 100, height: 100 }));
var redRect = new fabric.Rect({ fill: 'red', left: 75, width: 100, height: 100 });
var purpleRect = new fabric.Rect({ fill: 'purple', left: 0,
width: 100, height: 100 });
setTimeout(function() {
c.insertAt(purpleRect, 0, true);
c.insertAt(redRect, 1);
}, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.5.0/fabric.min.js"></script>
<canvas id="c" width="500" height="500" ></canvas>

As far as I know it is not possible to overwrite an object.
But this might help for your Note2:
In addition to Gabriele Petriolis comment, you can position the object in the stack after inserting:
Canvas.moveTo()
Canvas.bringToFront()
Canvas.bringForward()
Canvas.sendBackwards()
Canvas.sendToBack()
See: http://fabricjs.com/docs/fabric.Canvas.html

Related

How to change width\height of fabric.Path object? Params width\height in constructor don't work

I use fabric-js and try to pass width and height of fabric.Path via params in constructor like so:
const path = 'M16.2777 24.96C15.4876 26.3467 13.5124 26.3467 12.7223 24.96L0.27808 3.12C-0.512027 1.73333 0.475607 0 2.05582 0H26.9442C28.5244 0 29.512 1.73333 28.7219 3.12L16.2777 24.96Z';
const mapObject = new fabric.Path(path , {
width: 30, //
height: 30, //
left: 100,
top: 100,
fill: '#c9c9c9',
});
It works fine, but I can't affect to "image size". I pass different values but nothing changes.
Where am I wrong and how can I set size of fabric.Path object?
Path objects can be resized by changing the scaleX and scaleY values.
To achieve a specific width or height, you can also use the scaleToWidth() and scaleToHeight() methods.
http://fabricjs.com/docs/fabric.Path.html#scaleToHeight
http://fabricjs.com/docs/fabric.Path.html#scaleToWidth

Suspension points without some properties

How to use the suspension points without using some properties?
let foo = {
color: 'red',
backgroundColor: 'green',
width: 100,
height: 50,
}
let colors = {...foo} // removing width and height for this variable
Depends on your exact needs but there are two easy things you can do. If you really want to delete the properties from the object, use delete.
let colors = {...foo};
delete colors.width;
delete colors.height;
Otherwise you can set them to null/undefined.
let colors = {...foo, width: null, height: null};

[possible bug]Values in array as attribute of object set to undefined

I have encountered one of the most bizzare and frustrating behaviours yet. I have sample data:
var nodes = [ //Sample data
{
ID: 1,
Chart: 1,
x: 50,
y: 50,
width: 100,
height: 80,
color: "#167ee5",
text: "Start",
label: "Start",
targets: [2]
},
{
ID: 2,
Chart: 1,
x: 500,
y: 170,
width: 100,
height: 80,
color: "#167ee5",
text: "End",
label: "End",
targets: [3]
},
{
ID: 3,
Chart: 1,
x: 270,
y: 350,
width: 100,
height: 80,
color: "#167ee5",
text: "Mid",
label: "Mid",
targets: []
}
];
for my web application. The issue is with the targets attribute. As you can see it is array. However when I do
console.log(nodes[0]);
and inspect the result in the browser it shows that the value for targets at index 0 is undefined. Same for every other targets that has some values in them (whether 1 or more).
However if I do
console.log(nodes.[0].targets);
it prints out [2]. If I do Array.isArray(nodes[0].targets) it returns false, yet if I do console.log(nodes[0]) and inspect the result in the browser console, it shows that the object prototype is in fact Array and simply the value at index 0 is undefined.
It worked the day before and now it doesn't. The only thing I did was I restructured the object that uses this variable later. But the console log is being called before the object is even instantiated for the first time (and it doesn't change the nodes var anyway, only reads it).
Does anyone have any clues as to what might be causing this behaviour. If it helps I am using Paperscript and this code runs in the paperscript scope (as it did before when everything worked fine).
UPDATE
Ok after more blind debugging I have determined the block of code that causes the issue, how or why is completely beyond me.
Basically I define an object constructor beflow. The constructor loops through the nodes, makes Paperscript shapes and adds the targets to the arbitrary data attribute of the paperJS path object:
function Flowchart(nodes, chartdata) {
//Member vars. They are only used internally
var connections = [];
var shapes = [];
var connectors = [];
//Constructor operations
createShapes(nodes); //If I uncomment this, the problem goes away
//...
function createShapes(nodes) {
nodes.forEach(function (node) { //for each node data entry
console.log(node); //At this point, the targets are screwed up already
var point = new Point(node.x, node.y); //make a PaperJS point for placement
var size = new Size(node.width, node.height); //make a PaperJS size object
var shape = makeRectangle(point, size, 8, node.color); //Pass to the object instantiating function
shape.data = { //Store arbitrary data for programming reference.
ID: node.ID,
label: node.label,
text: node.text,
'connectors': {
to: [],
from: []
},
targets: node.targets //this is undefined
};
console.log(node.targets) //this logs [2] or [3] but not Array[1]...
shapes.push(shape); //Store reference for later
});
shapes.forEach(function (shape) { //loop though all drawn objects
if (shape.data.targets.length > 0) { //if shape has targets
var targets = _.filter(this.shapes, function (target) {
return _.contains(shape.data.targets, target.data.ID);
});
for (var i = 0; i < shape.data.targets.length; i++) {
shape.data.targets[i] = targets[i]; //Replace the ID-type reference with drawn object reference
}
}
});
}
//... The rest of the object
}
console.log(nodes);
//It doesnt seem to matter whether i put this before or after instantiating.
//It doesnt even matter IF I instantiate in the first place.
var chart = new Flowchart(nodes, chartdata);
This behaviour has been caused by the changes to how Chrome treats enumerable properties of objects. Because Chrome updates silently, it's impossible to notice.
It must have been causing me a lot of headache if I remembered the cause after all this time... (Also it's embarrassing how bad I was at writing questions, but I guess that I realise it means I have progressed since then somewhat).

make objects push against each other in Raphael.js

me again with a very specific question about Raphael.js . What I would like to do is, that rectangles push against each other. Lets say I have following situation that each rectangle is side by side example picture and I let the one in the middle transform big:
r.click(function() { r.animate({ width: 100, height: 100 }, 500); });
how can I handle it, that the other ones move correctly away like the transformation.
I've tried it by transforming every by hand, but the problem is, my markup isnt that simple.
Thanks in advance.
Its difficult to know a specific answer without seeing any code and knowing what the markup issues or or how you can tie some maths into calculating the figures, it will likely be more complicated, but you may be able to calculate the amount moved by the rects compared to the new width of the original rect. This shows an example of one way you could maybe approach it... jsfiddle here http://jsfiddle.net/R8avm/3/
Edit: There's an updated fiddle which has some basic maths which can sort of figure the end x,y if you know which direction the cubes will be in. http://jsfiddle.net/R8avm/4/ It will need more complex stuff for more rects moving at different angles if thats needed.
<div id="container"></div>
paper = Raphael( "container",400,400);
var rects = [ [ 100,50,50,50, 150,50 ], //x,y,w,h,newx,newy
[ 50,100,50,50, 50,150 ],
[ 50,0,50,50, 50, -50 ],
[ 0,50,50,50, -50,50 ],
];
var newRects = new Array(4);
var r = paper.rect(50,50,50,50).attr({ fill: '#123' });
for (var a=0; a<rects.length; a++) {
newRects[a] = paper.rect( rects[a][0], rects[a][1], rects[a][2], rects[a][3]);
};
r.click(function() {
r.animate({ width: 150, height: 150, x: 0, y: 0 }, 500);
for (var a=0; a<rects.length; a++) {
newRects[a].animate({ x: rects[a][4] , y: rects[a][5] }, 500);
};
});

How to change speed of a sprite animation on Spritely?

I've been reading the Spritely documentation over and over and I can't find the solution.
I have a sprite made of 4 frames.
I want to animate it endlessly but for each iteration : 1st frame at 99 ms and next 3 frames at 11ms.
How do I achieve the slow down on the first frame?
The syntaxe for doing this isn't clear at all and everything I've tried failed.
Thanks in advance for the help :)
$('#test')
.sprite({fps: 6, no_of_frames: 4})
});
From what I was able to find, some of the calls allow speed, like random for instance. So the example they give:
$('#bird')
.sprite({fps: 8, no_of_frames: 3})
.spRandom({
top: 70,
left: 100,
right: 200,
bottom: 340,
speed: 4000,
pause: 3000
});
allows speed. The sprite call appears not to. The other speed attribute appears to say on backgrounds, but it might be worth a shot. Their example:
$('#trees').pan({fps: 30, speed: 2, dir: 'left'});
or
$('#hills').spSpeed(20);
As far as the second part of your question, sprites can do whatever on N frame by the on_frame function. Again, per their documentation:
on_frame: { // note - on_frame is an object not a function
8: function(obj) { // called on frame 8
obj.spState(2); // change to state 2 (row 2) on frame 8
},
16: function(obj) { // called on frame 16
obj.spState(3); // change to state 3 (row 3) on frame 16
}
}

Categories

Resources