Reputation: 403
Say I have multiple objects selected on a fabric.js canvas and can get this group of objects using getActiveGroup. Can anyone provide an example of how to copy and paste the group into a new group, where:
(a) each object in the copied group retains its relative position to the other copied objects
(b) the copied group as a whole is positioned at some specified x,y position
(c) the copied group objects are selected as a group after the paste, but if the selection is cleared they are treated as individual objects on the canvas
I have tried pasting by cloning and adding the cloned group like this:
canvas.getActiveGroup().clone(function(clone) {
clone.left = 100;
clone.top = 100;
canvas.add(clone);
canvas.deactivateAll();
canvas.setActiveGroup(clone).renderAll();
});
Now after this code is run the cloned group objects seem to be added, positioned and selected ok, but as soon as I click the canvas to clear the selection the cloned group objects jump to a different spot on the canvas. Also the cloned objects are not individually selectable and the selection location is out of sync with the new object location. Anyways I'm sure I'm missing something but I'm not sure what. Any help is appreciated.
Upvotes: 2
Views: 4870
Reputation: 11
Had this same issue and your answer helped me a lot.
Here is a fiddle showing how to do it.
https://jsfiddle.net/Lcdrohk4/1/
canvasWrapper.addEventListener('keydown', function(e) {
//Copy paste function for canvas objects
//If ctrl is pressed, set ctrlDown to true
if (e.keyCode == 17) ctrlDown = true;
//Handle ctrl+c
if (ctrlDown && e.keyCode == 67) {
//reset array with copied objects
copiedObjects = [];
//Get the activeObject and the ActiveGroup
var activeObject = canvas.getActiveObject(),
activeGroup = canvas.getActiveGroup();
//If multiple items are selected the activeGroups will be true
if (activeGroup) {
//get the objects from the activegroup
var objectsInGroup = activeGroup.getObjects();
//Discard the activeGroup
canvas.discardActiveGroup();
//Push all items from the activeGroup into our array
objectsInGroup.forEach(function(object) {
copiedObjects.push(object);
});
} else if (activeObject) { //If one item is selected then acriveObject will be true
copiedObjects.push(activeObject); //push our selected item into the array
}
};
//handle ctrl+v
if (ctrlDown && e.keyCode == 86) {
var count = 0;
//Check if we have only one item we want to copy
if (copiedObjects.length == 1) {
//check if we can handle async cloning
if (fabric.util.getKlass(copiedObjects[0].type).async) {
copiedObjects[0].clone(function(clone) {
//Add our item
pasteOne(clone);
//Select our item
selectAll(1);
});
} else { //Sync cloning
//Add our item
pasteOne(copiedObjects[0].clone());
//Select our item
selectAll(1);
}
//Handle multiple item copying
} else if(copiedObjects.length > 1) {
for (var index = (copiedObjects.length - 1); index >= 0; index--) {
//check if we can handle async cloning
if (fabric.util.getKlass(copiedObjects[index].type).async) {
copiedObjects[index].clone(function(clone) {
//Add our item
pasteOne(clone);
count++;
//if we have added all our items we can now select them
if (count == copiedObjects.length) {
selectAll(copiedObjects.length);
}
});
}else{ //sync cloning
//Add our item
pasteOne(copiedObjects[index].clone());
count++;
//if we have added all our items we can now select them
if (count == copiedObjects.length) {
selectAll(copiedObjects.length);
}
}
}
}
}
//Delete selected items with the delete button
if(e.keyCode == 46){
var activeObject = canvas.getActiveObject(),
activeGroup = canvas.getActiveGroup();
if (activeGroup) {
var objectsInGroup = activeGroup.getObjects();
canvas.discardActiveGroup();
objectsInGroup.forEach(function(object) {
canvas.remove(object);
});
}
else if (activeObject) {
canvas.remove(activeObject);
}
}
}, false);
//Set ctrlDown to false when we release the ctrl key
canvasWrapper.addEventListener('keyup', function(e) {
if (e.keyCode == 17) ctrlDown = false;
});
//Add our item to the canvas
function pasteOne(clone) {
clone.left += 100; //add 100 to the left position
clone.top += 100; //add 100 to the top position
clone.set('canvas', canvas); //Set the canvas attribute to our canvas
clone.setCoords(); //Must call this when we cahnged our coordinates
canvas.add(clone); //Add the item
};
//Select all copied items. numberOfItems is the count of how many items where copied
function selectAll(numberOfItems) {
// Clear the selection
canvas.deactivateAll();
canvas.discardActiveGroup();
//new array for handling the newly pasted objects
var objs = new Array();
//get all objects in the canvas
var canvasObjects = canvas.getObjects();
//counter for keeping track how many items we have in "objs"
var count = 0;
//loop from the end of all items in the canvas
for (var index = (canvasObjects.length - 1); index >= 0; index--) {
//Push the item into objs and set the active state to true
if (count < numberOfItems) objs.push(canvasObjects[index].set('active', true));
count++;
}
//Create new fabric group with the copied objects
var group = new fabric.Group(objs, {
originX: 'center',
originY: 'center'
});
//set the group as the new active group
canvas.setActiveGroup(group.setCoords()).renderAll();
}
Upvotes: 1
Reputation: 403
Thanks to Nistor for helping to get me started. I had to do some additional work to get the result I wanted. Here is my paste function (note that I am using Angular but others can adapt as necessary). Objects must be copied to the clipboard array prior to this function being called.
// Handle a paste request
$scope.paste = function() {
// Make sure we have something in the clipboard
if ($scope.shared.clipboard.length == 0) {
return;
}
// Check if we have single or multiple objects on the clipboard
if ($scope.shared.clipboard.length == 1) {
$scope.shared.clipboard[0].clone(function(clone) {
pasteOne(clone);
$scope.select(clone);
});
} else {
var group = new fabric.Group();
for (var index = ($scope.shared.clipboard.length - 1); index >= 0; index--) {
$scope.shared.clipboard[index].clone(function(clone) {
pasteOne(clone);
group.addWithUpdate(clone);
// Clone is async so wait until all group objects are cloned before selecting
if (group.size() == $scope.shared.clipboard.length) {
group.setCoords();
$scope.select(group);
}
});
}
}
};
This function keeps the relative offsets for each pasted object. The paste position can be set to some arbitrary value:
// Paste a single clone onto the canvas
var pasteOne = function(clone) {
clone.left += $scope.shared.pastePosition.x;
clone.top += $scope.shared.pastePosition.y;
clone.setCoords();
$scope.canvas.add(clone);
};
This code updates the selection. Note that to select both the group and the individual objects within the group I needed to call object.set('active'), true) for each. $scope.isGroup(select) just checks to see if the select type is 'group'.
// Update the selection
$scope.select = function(select) {
// Clear the selection
$scope.canvas.deactivateAll();
$scope.canvas.discardActiveGroup();
// Handle group vs single object selections
if ($scope.isGroup(select)) {
$scope.canvas.setActiveGroup(select);
select.forEachObject(function(object) {
object.set('active', true);
});
} else {
$scope.canvas.setActiveObject(select);
}
// Refresh the canvas
$scope.canvas.renderAll();
}
Upvotes: 0
Reputation: 1266
You should try canvas.getActiveGroup().forEachObject(function(o)
for group type objects and then clone them.
I made you a jsFiddle with cloning a group.
Upvotes: 1