I have a simple fabric js based application where I will let users add shapes connect them and animate them.
Following is my JS
var canvas;
window.newAnimation = function(){
canvas = new fabric.Canvas('canvas');
}
window.addRect = function(){
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20,
});
canvas.add(rect);
}
window.addCircle = function(){
var circle = new fabric.Circle({
radius: 20, fill: 'green', left: 100, top: 100
});
canvas.add(circle);
}
This is my Fiddle. You can click on new animation and then add objects as of now.
I want the user to select some object and then also be able to delete it I am not sure how. I found this Delete multiple Objects at once on a fabric.js canvas in html5 But i was not able to implement it successfully. I basically want users to be able to select an object and delete it.
Since new version of fabric.js was released - you should use:
canvas.remove(canvas.getActiveObject());
Edit: This is for older versions now.
You can use the remove() method, eg.
window.deleteObject = function() {
canvas.getActiveObject().remove();
}
jsfiddle
Delete all selected objects:
canvas.getActiveObjects().forEach((obj) => {
canvas.remove(obj)
});
canvas.discardActiveObject().renderAll()
I am using Fabric JS 2.3.6.
I wanted to allow the user to select one or more objects on the canvas and delete them by clicking the delete button.
Removed methods from old versions
The following methods are no longer available since the introduction of ActiveSelection:
setActiveGroup(group);
getActiveGroup();
deactivateAll();
discardActiveGroup();
deactivateAllWithDispatch();
Here is my code that works great for me and hopefully you as well.
$('html').keyup(function(e){
if(e.keyCode == 46) {
deleteSelectedObjectsFromCanvas();
}
});
function deleteSelectedObjectsFromCanvas(){
var selection = canvas.getActiveObject();
if (selection.type === 'activeSelection') {
selection.forEachObject(function(element) {
console.log(element);
canvas.remove(element);
});
}
else{
canvas.remove(selection);
}
canvas.discardActiveObject();
canvas.requestRenderAll();
}
It's pretty simple actually.
Just use Fabric's event handling to manage the object selection, and then fire the delete function for whatever object is selected.
I'm using the canvas selection events to cover all the objects of the canvas. The idea is to add a delete button, keeping it hidden unless needed, and then handling its display on canvas selection events.
Deletion is made easy with the help of remove property of the Fabric library, which obviously triggers when the delete button is clicked.
Here is some sample code to demo what I said above.
// Grab the required elements
var canvas = new fabric.Canvas('c'),
delBtn = document.getElementById('delete')
// Hide the delete button until needed
delBtn.style.display = 'none'
// Initialize a rectangle object
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 100,
height: 50
})
// Initialize a circle object
var circle = new fabric.Circle({
left: 250,
top: 100,
radius: 20,
fill: 'purple'
})
// Add objects to the canvas
canvas.add(rect)
canvas.add(circle)
// When a selection is being made
canvas.on({
'selection:created': () => {
delBtn.style.display = 'inline-block'
}
})
// When a selection is cleared
canvas.on({
'selection:cleared': () => {
delBtn.style.display = 'none'
}
})
// Rmove the active object on clicking the delete button
delBtn.addEventListener('click', e => {
canvas.remove(canvas.getActiveObject())
})
body {
background-color: #eee;
color: #333;
}
#c {
background-color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
<h4>Select an object</h4>
<canvas id="c" width="600" height="200"></canvas>
<input type="button" id="delete" value="Delete selection">
Easy, wasn't it? Cheers!
On further improving #Rahul answer, we can also support deletion of selected object using key events like 'Delete', 'Backscape'.
// Grab the required elements
var canvas = new fabric.Canvas('c'),
delBtn = document.getElementById('delete'),
wrapper= document.getElementById('canvasWrapper')
// Hide the delete button until needed
delBtn.style.display = 'none'
// Initialize a rectangle object
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 100,
height: 50
})
// Initialize a circle object
var circle = new fabric.Circle({
left: 250,
top: 100,
radius: 20,
fill: 'purple'
})
// Add objects to the canvas
canvas.add(rect)
canvas.add(circle)
// When a selection is being made
canvas.on({
'selection:created': () => {
delBtn.style.display = 'inline-block'
}
})
// When a selection is cleared
canvas.on({
'selection:cleared': () => {
delBtn.style.display = 'none'
}
})
// Rmove the active object on clicking the delete button
delBtn.addEventListener('click', e => {
canvas.remove(canvas.getActiveObject())
})
//Remove using keyboard events
wrapper.addEventListener('keyup', e => {
if (
e.keyCode == 46 ||
e.key == 'Delete' ||
e.code == 'Delete' ||
e.key == 'Backspace'
) {
if (canvas.getActiveObject()) {
if (canvas.getActiveObject().isEditing) {
return
}
canvas.remove(canvas.getActiveObject())
}
}
})
body {
background-color: #eee;
color: #333;
}
#c {
background-color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
<h4>Select an object</h4>
<div tabIndex="1000"id="canvasWrapper" #keyup="checkDelete($event)" >
<canvas id="c" width="600" height="200"></canvas>
</div>
<input type="button" id="delete" value="Delete selection">
Delete object in Fabricjs. This works completely fine.
this.canvas.getActiveObjects().forEach((o) => {
this.canvas.remove(o);
});
you can delete active object by using backspace key
$(document).keydown(function(event){
if (event.which == 8) {
if (canvas.getActiveObject()) {
canvas.getActiveObject().remove();
}
}
});
Related
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
}
i am working on online print media application like 'vistaprint.in' where user can modify premade designs on fabric js canvas. in fabric.js object there is no unique identifier for objects...all the text objects create a input for each them so that user can directly change desired text by just changing in input...you can see in screenshot in right side of canvas there are input for each text object...what i am doing now is when input changes i loop through all text objects and match input text and text object text if it matches then i update new text to that object..this works fine for first time but not after that...i tried but unable to find what to do to solve this.
This is how canvas and input looks like now
Screenshot of app
and this is the function which i created now for changing text of canvas
$(document).on('input', '#cardalltexthex input', function(){
v = $(this).attr('text');
newtext = $(this).val();
objs = canvas.getObjects();
objs.forEach(function(e) {
if (e && e.type === 'i-text') {
console.log(e.text);
console.log(newtext);
if (e.text == v) {
e.setText(newtext);
canvas.renderAll();
}
}
});
});
You need to specify id property to fabric object and check that property with your input as below sample
No need to do if (e.text == v) just set all fabric object with some unique id and compare that with your input.
var text;
var canvas;
canvas = new fabric.Canvas('c');
text1 = new fabric.Text("Text", {
id: "cardalltexthex1",
fontSize: 70,
selectable: false,
left: 100,
top: 0,
text: "text1",
fill: '#f00'
});
canvas.add(text1);
text2 = new fabric.Text("Text", {
id: "cardalltexthex2",
fontSize: 70,
selectable: false,
left: 100,
top: 100,
text: "text2",
fill: '#f00'
});
canvas.add(text2);
$('.input').on('keyup', function() {
id = $(this).attr('id');
val = $(this).attr('data-text');
newtext = $(this).val();
input = $(this);
objs = canvas.getObjects();
objs.forEach(function(obj) {
if (obj && obj.text == val) {
obj.setText(newtext);
input.attr("data-text", newtext);
canvas.renderAll();
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
<canvas id="c" width=500 height=250></canvas>
<input class="input" data-text="text1" value="text1" id="cardalltexthex1" />
<input class="input" data-text="text2" value="text2" id="cardalltexthex2" />
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
I am using fabric.js for image manipulation and it works great, but I need the controls to be always visible even when I click outside the object/image.
By default they are visible only when you click the objec/image, if you click outside of it the controls disappear.
Is it possibile to do so?
Thank you.
Unfortunately, there is no built-in method in FabricJS, to achieve this at the moment.
However, here is a workaround (function) , which will simulate this functionality ...
function showControls(...objs) {
objs.forEach(obj => {
obj.set('active', true);
canvas.renderAll();
canvas.on('mouse:down', function(e) {
obj.set('active', true);
});
})
}
after adding the image object on the canvas, call the above function along with passing the image object as a parameter, that you wish to show controls for.
ᴅᴇᴍᴏ
var canvas = new fabric.Canvas('c');
// add rectangle (for demo purposes only)
var rect = new fabric.Rect({
top: 100,
left: 290,
width: 100,
height: 100,
fill: '#07C',
originX: 'center',
originY: 'center',
transparentCorners: false
});
canvas.add(rect);
// add image (for demo purposes only)
fabric.Image.fromURL('https://i.imgur.com/Q6aZlme.jpg', function(img) {
img.set({
top: 100,
left: 110,
width: 100,
height: 100,
originX: 'center',
originY: 'center',
transparentCorners: false
})
canvas.add(img);
showControls(img); // pass an object that you wish to show controls for
});
// always show controls (multi-object support)
function showControls(...objs) {
objs.forEach(obj => {
obj.set('active', true);
canvas.renderAll();
canvas.on('mouse:down', function(e) {
obj.set('active', true);
});
})
}
canvas{border:1px solid #ccc}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.11/fabric.min.js"></script>
<canvas id="c" width="400" height="200"></canvas>
not perfect, but a start
// fabricjs object - always show controls
//fabric.Object.prototype.render = (function (render) {
fabric.Image.prototype.render = (function (render) {
return function (ctx) {
render.apply(this, arguments)
// show controls
// but most controls are not usable
// until you activate [click] the object
this._renderControls(ctx)
// activate this object
// to make all controls usable
// only one active object per canvas
// when another object is active
// the controls become not usable
if (!this.canvas._activeObject)
this.canvas._activeObject = this
}
//})(fabric.Object.prototype.render)
})(fabric.Image.prototype.render)
// fabricjs object - activate on mouseover
// this is just a quick hack
// to make controls always usable
//fabric.Object.prototype.initialize = (function (initialize) {
fabric.Image.prototype.initialize = (function (initialize) {
return function () {
initialize.apply(this, arguments)
this.on('mouseover',
function(event) {
this.canvas.setActiveObject(this)
this.canvas.renderAll() // TODO cheaper?
})
}
//})(fabric.Object.prototype.initialize)
})(fabric.Image.prototype.initialize)
edit 2016, Oct 24
I've an idea about this feature by using 'pattern' which seems to be a good idea.
I tried to prove my thoughts with this pen, its still buggy but at least we can set the image(pattern) position and keep the original image when double click.
I'am not very good at javascript, So if you are interest in this please help to make this more useable, any discussions/thoughts or code correction is welcome.
http://codepen.io/wushan/pen/LRrQEL?editors=1010
// Create Canvas
var canvas = this.__canvas = new fabric.CanvasEx('c', {
preserveObjectStacking: true
});
fabric.Object.prototype.transparentCorners = false;
// Global Settings
var url = "http://fabricjs.com/assets/pug.jpg";
//Make the Pattern by url
function createMaskedImage(url) {
//Load Image
fabric.Image.fromURL(url, function(img) {
img.scaleToWidth(300);
//Make a Pattern
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(img);
var pattern = new fabric.Pattern({
source: function() {
patternSourceCanvas.setDimensions({
width: img.getWidth(),
height: img.getHeight()
});
return patternSourceCanvas.getElement();
},
repeat: 'no-repeat'
});
console.log(pattern.offsetX)
console.log(pattern.offsetY)
console.log(img.getWidth()) // 縮小後 (*scale)
console.log(img.width) // 原尺寸
//Mask (can be any shape ex: Polygon, Circles....)
var rect = new fabric.Rect({
width: 200,
height: 200,
left: 150,
top: 100,
fill: pattern
})
//Bind Double Click Event from fabric.ext
//https://github.com/mazong1123/fabric.ext
rect.on('object:dblclick', function (options) {
//Pass pattern out
enterEditMode(rect, img);
});
canvas.add(rect);
canvas.setActiveObject(rect);
});
}
function enterEditMode(mask, image) {
image.left = mask.left;
image.top = mask.top;
// New Image
// Fake Crop Area (fixed)
var rect = new fabric.Rect({
width: mask.width,
height: mask.height,
left: mask.left,
top: mask.top,
fill: '#000000',
opacity: 0.8,
selectable: false
})
canvas.remove(mask);
canvas.add(image);
image.on('object:dblclick', function (options) {
//Flatten
flatten(rect, image);
});
canvas.add(rect);
// console.log(JSON.stringify(canvas));
}
function flatten(mask, image) {
//Make a Pattern
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(image);
var pattern = new fabric.Pattern({
source: function() {
patternSourceCanvas.setDimensions({
width: image.getWidth(),
height: image.getHeight()
});
return patternSourceCanvas.getElement();
},
repeat: 'no-repeat'
});
//Offsets
pattern.offsetX = image.left - mask.left - image.left;
pattern.offsetY = image.top - mask.top - image.top;
var rect = new fabric.Rect({
width: mask.width,
height: mask.height,
left: mask.left,
top: mask.top,
fill: pattern
})
//Bind Double Click Event from fabric.ext
//https://github.com/mazong1123/fabric.ext
rect.on('object:dblclick', function (options) {
//Pass pattern out
enterEditMode(rect, image);
});
canvas.remove(mask);
canvas.remove(image);
canvas.add(rect);
canvas.setActiveObject(rect);
canvas.renderAll();
}
//Button Events
//Create
document.getElementById('createMaskedImage').addEventListener('click', function () {
createMaskedImage(url);
});
Test this :
click 'create'
double click on the image object
move/scale the image
double click the image to flatten the obejct.
Know issue:
when cutting the image without scale, the image position looks correct but there is a wired transparent space.
It generates a lot duplicate objects on the scene...
Original Post
I've been working with fabric.js for a few month, haven't seen any example or discussions about a mask/crop system which is behaves like canva.com ( which is way easy to understand for users. )
the object looks like this:
when double clicked on a group/mask, it shows the original image with an unselectable mask, you can move/scale the image whatever you need and click 'OK' to made the change without modifying the original image.
I'd like to know if there is any possible solutions about making this in fabricjs, or maybe some thoughts about this issue is welcome.
Thank you a lot !