How can I delete a fabric canvas arrow? - javascript

I created an arrow using Fabric.js group functionality by grouping Rectangle and Triangle.
Unfortunately I am not able to delete it as a group.
I figured out that getActiveObject() for a single object works fine. Instead, while debugging getActiveGroup() gives error.
var canvas = new fabric.Canvas('canvas');
canvas.setHeight(window.innerHeight * 0.75);
canvas.setWidth(window.innerWidth * 0.75);
//-------------------------Group - Rectangle and Triangle----------------------------------------------
window.addArrow = function() {
var rect = new fabric.Rect({
left: 0,
top: 0,
stroke: 'red',
fill: 'red',
width: 1,
height: 50,
});
rect.hasRotatingPoint = true;
canvas.add(rect);
var triangle = new fabric.Triangle({
width: 10,
height: 10,
fill: 'red',
left: -4,
top: -10
});
var group = new fabric.Group([rect, triangle], {
left: 150,
top: 100,
angle: 90
});
canvas.add(group);
}
//-------------------------Group Delete----------------------------------------------
window.deleteObject = function() {
return canvas.getActiveObject() == null ? canvas.getActiveGroup() : canvas.getActiveObject();
}
function getActiveGroup() {
canvas.getActiveGroup().forEachObject(function(o) {
canvas.remove(o)
});
}
function getActiveObject() {
canvas.remove(canvas.getActiveObject());
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js"></script>
<canvas id="canvas" width="800" height="600" style="border:1px solid red;"></canvas>
<button onClick="addArrow()">Arrow</button>
<button onClick="deleteObject()">Delete</button>

Because you didn't set any active object you cannot use getActiveObject.
In this case you may use getObjects:
canvas.getObjects('group') // in order to get the group
canvas.getObjects('rect') // in order to get the rectangle
var canvas = new fabric.Canvas('canvas');
canvas.setHeight(window.innerHeight * 0.75);
canvas.setWidth(window.innerWidth * 0.75);
//-------------------------Group - Rectangle and Triangle----------------------------------------------
window.addArrow = function() {
var rect = new fabric.Rect({
left: 0,
top: 0,
stroke: 'red',
fill: 'red',
width: 1,
height: 50,
});
rect.hasRotatingPoint = true;
canvas.add(rect);
var triangle = new fabric.Triangle({
width: 10,
height: 10,
fill: 'red',
left: -4,
top: -10
});
var group = new fabric.Group([rect, triangle], {
left: 150,
top: 100,
angle: 90
});
canvas.add(group);
}
//-------------------------Group Delete----------------------------------------------
window.deleteObject = function() {
// remove group....
canvas.getObjects('group').forEach(function(ele,idx) {
canvas.remove(ele);
});
canvas.getObjects('rect').forEach(function(ele,idx) {
canvas.remove(ele);
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js"></script>
<canvas id="canvas" width="800" height="600" style="border:1px solid red;"></canvas>
<button onClick="addArrow()">Arrow</button>
<button onClick="deleteObject()">Delete</button>

use sub classing in fabric.js for creating arrow object so that single object is only needed. So handling will be easier.
[http://fabricjs.com/fabric-intro-part-3#subclassing][1]

Related

Html canvas element's getImageData doesn't match content of Fabricjs canvas

I added a fabric Rect at (80, 80) and then called getImageData at that location. However, the data I get from that location doesn't match the Rect I placed there. Instead, I find the data around (60, 60).
var canvas = new fabric.Canvas('fabric-canvas');
canvas.setHeight(800);
canvas.setWidth(800);
var rect = new fabric.Rect({
left: 80,
top: 80,
fill: 'red',
width: 20,
height: 20
});
canvas.add(rect);
// This doesn't work
let data = canvas.getContext('2d').getImageData(80, 80, 20, 20);
canvas.getContext('2d').putImageData(data, 0, 0);
// This "works"
data = canvas.getContext('2d').getImageData(60, 60, 20, 20);
canvas.getContext('2d').putImageData(data, 100, 0);
Here's a fiddle demo: https://jsfiddle.net/2wxdb8ua/13/
How might this be possible?
The problem is certainly caused by some zooming, either due to your monitor being an high-res monitor (a.k.a retina), or browser/OS zoom being applied on the page.
You can either make the calculation yourself world_coord * window.devicePixelRatio:
var canvas = new fabric.Canvas('fabric-canvas');
canvas.setHeight(800);
canvas.setWidth(800);
var rect = new fabric.Rect({
left: 80,
top: 80,
fill: 'red',
width: 20,
height: 20
});
canvas.add(rect);
let ctx = canvas.getContext('2d').getImageData(...[80, 80, 20, 20].map(fixCoords));
canvas.getContext('2d').putImageData(ctx, ...[0, 0].map(fixCoords));
function fixCoords( coord ) {
return coord * window.devicePixelRatio;
}
canvas#fabric-canvas {
background: white;
border: 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.1/fabric.min.js"></script>
<canvas id='fabric-canvas'></canvas>
Or disable fabric's auto-scaling by using the enableRetinaScaling parameter of the initOptionObject:
var canvas = new fabric.Canvas('fabric-canvas', {
enableRetinaScaling: false
});
canvas.setHeight(800);
canvas.setWidth(800);
var rect = new fabric.Rect({
left: 80,
top: 80,
fill: 'red',
width: 20,
height: 20
});
canvas.add(rect);
let ctx = canvas.getContext('2d').getImageData(80, 80, 20, 20);
canvas.getContext('2d').putImageData(ctx, 0, 0);
canvas#fabric-canvas {
background: white;
border: 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.1/fabric.min.js"></script>
<canvas id='fabric-canvas'></canvas>
But note that with the latter solution, your canvas will certainly become blurry on high-res monitors.

Not able to delete shape objects from fabric canvas

I uploaded an image as background image, so that I am able to draw circle and rectangle on top of it.
But, when I try to delete any shape, I am not able to do that.
I tried debugging and it just says Unexpected token ) on Delete
I am just not able to figure out what exactly is the problem here.
//-----------------------------Getting hold of Canvas----------------------------
var canvas = new fabric.Canvas('canvas');
canvas.setHeight(window.innerHeight * .75);
canvas.setWidth(window.innerWidth * .75);
drawBackground();
//--------------------------Image Rendering--------------------------------------
function drawBackground() {
fabric.Image.fromURL('https://upload.wikimedia.org/wikipedia/commons/f/f9/Phoenicopterus_ruber_in_S%C3%A3o_Paulo_Zoo.jpg', function(img) {
img.scaleToWidth(window.innerWidth * .75);
img.scaleToHeight(window.innerHeight * .75);
canvas.setBackgroundImage(img);
canvas.renderAll();
});
}
//------------------------Reset--------------------------------------------------
/*window.reset = function(){
canvas = new fabric.Canvas('canvas');
drawBackground();
}*/
//------------------------Rectangle----------------------------------------------
window.addRect = function() {
var rect = new fabric.Rect({
left: 0,
top: 0,
stroke: 'red',
fill: 'rgba(255,0,0,.4)',
width: 50,
height: 50,
});
rect.hasRotatingPoint = false;
canvas.add(rect);
}
//---------------------Circle----------------------------------------------------
window.addCircle = function() {
var circle = new fabric.Circle({
left: 0,
top: 0,
radius: 20,
stroke: 'green',
fill: 'transparent',
});
circle.hasRotatingPoint = false;
canvas.add(circle);
}
//--------------------Delete Objects---------------------------------------------
window.delete = function() {
canvas.remove(canvas.getActiveObject());
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js">
</script>
<canvas id="canvas" width="800" height="600" style="border:1px solid red;"></canvas>
<!--<button onClick="reset()">Reset</button> !-->
<button onClick="addCircle()">Circle</button>
<button onClick="addRect()">Box</button>
<button onClick="delete()">Delete</button>
delete is a reserved word and cant be used as a function on the window object.
A helpful link:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
You use delete as function inline right here:
<button onClick="delete()">Delete</button>

How can i able to move image beneath rectangle in canvas?

I could select the background image from inside rectangle, but not able to move.
To move the image, i should go outside of the rectangle.
I know that the rectangle is created over the image, I'm looking for any option to move the image as it is selectable from inside rectangle.
My canvas screenshot
This is how i set the image and rectangle.
changeImage(innercanvasHeight, innercanvasWidth) {
const base_image = new Image();
base_image.crossOrigin = 'Anonymous';
base_image.src = 'assets/images/1wal.jpg';
fabric.Image.fromURL(base_image.src, (myImg) => {
const img1 = myImg.set({left: 160, top: 80, width: 600, height:
400, id: 'wallpaper'});
this.FabriCanvas.add(img1).setActiveObject(img1);
const hiddenImg = document.createElement('img');
hiddenImg.src = this.FabriCanvas.getActiveObject().toDataURL();
hiddenImg.id = 'target';
hiddenImg.style.display = 'none';
document.body.appendChild(hiddenImg);
this.innerCanvas(innercanvasHeight, innercanvasWidth);
});
innerCanvas(height, width) {
this.innercanvas = this.FabriCanvas.add(new fabric.Rect({
left: 160,
top: 80,
id: 'innerCan',
fill: 'transparent',
stroke: '#fff',
strokeWidth: 1,
width: width,
height: height,
selectable: false
}));
this.FabriCanvas.renderAll();
}
Use preserveObjectStacking so it wont come up while dragging, and use perPixelTargetFind to click through the object if it is transparent.
DEMO
var canvas = new fabric.Canvas('canvas',{
preserveObjectStacking: true
});
var image = new fabric.Image('');
var rect = new fabric.Rect({
left: 160,
top: 80,
id: 'innerCan',
fill: 'transparent',
stroke: '#fff',
strokeWidth: 1,
width: 100,
height: 100,
selectable: false,
perPixelTargetFind : true
});
canvas.add(image,rect);
image.setSrc('//fabricjs.com/assets/pug.jpg',function(img){
img.set({ scaleX:canvas.width/img.width,scaleY: canvas.height/img.height});
img.setCoords();
canvas.renderAll();
})
canvas{
border: 2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='canvas' width=300 height=300></canvas>

Sending multiple objects forward changes their order (z-index)

The following snippet has a green square above a red square
Select both squares by dragging over them.
Click the bring forward button
After clicking bring forward the squares have switched order. It is my understanding that the items should stay in the same order, but be moved increasingly above other non-selected items as the button is further clicked.
If you deselect, and repeat the experiment you will see that they switch again.
Any ideas?
var canvas = new fabric.Canvas('c',
{
preserveObjectStacking : true
});
var rect = new fabric.Rect({
left: 10, top: 10,
fill: 'red',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect);
var rect2 = new fabric.Rect({
left: 40, top: 40,
fill: 'green',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect2);
$("#bringForward").click(function()
{
var items = canvas.getActiveObject() || canvas.getActiveGroup();
if(items)
items.bringForward();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.6/fabric.min.js"></script>
<button id="bringForward">Bring Forward</button>
<canvas id="c" width="640" height="480"></canvas>
This can be considered a bug or not, depending on what do you expect the function to do.
The documentation for the feature says:
Moves an object or a selection up in stack of drawn objects
And is actually doing so.
The object on top cannot go more on top, the one under can and goes.
Still for a dev this can look like a weird behaviour, to me not really. But guess is personal.
Here is your widget with a modified snippet to try a better solution.
var removeFromArray = fabric.util.removeFromArray;
// modified function to avoid snapping
fabric.StaticCanvas.prototype.bringForward = function (object, intersecting) {
if (!object) {
return this;
}
var activeGroup = this._activeGroup,
i, obj, idx, newIdx, objs, latestIndex;
if (object === activeGroup) {
objs = activeGroup._objects;
latestIndex = this._objects.length;
for (i = objs.length; i--;) {
obj = objs[i];
idx = this._objects.indexOf(obj);
if (idx !== this._objects.length - 1 && idx < latestIndex - 1) {
newIdx = idx + 1;
latestIndex = newIdx;
removeFromArray(this._objects, obj);
this._objects.splice(newIdx, 0, obj);
} else {
latestIndex = idx;
}
}
}
else {
idx = this._objects.indexOf(object);
if (idx !== this._objects.length - 1) {
// if object is not on top of stack (last item in an array)
newIdx = this._findNewUpperIndex(object, idx, intersecting);
removeFromArray(this._objects, object);
this._objects.splice(newIdx, 0, object);
}
}
this.renderAll && this.renderAll();
return this;
};
var canvas = new fabric.Canvas('c',
{
preserveObjectStacking : true
});
var rect = new fabric.Rect({
left: 10, top: 10,
fill: 'red',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect);
var rect2 = new fabric.Rect({
left: 40, top: 40,
fill: 'green',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect2);
var rect3 = new fabric.Rect({
left: 70, top: 70,
fill: 'blue',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect3);
var rect4 = new fabric.Rect({
left: 100, top: 100,
fill: 'orange',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect4);
$("#bringForward").click(function()
{
var items = canvas.getActiveObject() || canvas.getActiveGroup();
if(items)
items.bringForward();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.6/fabric.min.js"></script>
<button id="bringForward">Bring Forward</button>
<canvas id="c" width="640" height="480"></canvas>

How to select covered objects via mouse in fabricJS?

I'm trying to develop a way to select objects that are layered below and (totally) covered by other objects. One idea is to select the top object and then via doubleclick walk downwards through the layers. This is what I got at the moment:
var canvas = new fabric.Canvas("c");
fabric.util.addListener(canvas.upperCanvasEl, "dblclick", function (e) {
var _canvas = canvas;
var _mouse = _canvas.getPointer(e);
var _active = _canvas.getActiveObject();
if (e.target) {
var _targets = _canvas.getObjects().filter(function (_obj) {
return _obj.containsPoint(_mouse);
});
//console.warn(_targets);
for (var _i=0, _max=_targets.length; _i<_max; _i+=1) {
//check if target is currently active
if (_targets[_i] == _active) {
//then select the one on the layer below
_targets[_i-1] && _canvas.setActiveObject(_targets[_i-1]);
break;
}
}
}
});
canvas
.add(new fabric.Rect({
top: 25,
left: 25,
width: 100,
height: 100,
fill: "red"
}))
.add(new fabric.Rect({
top: 50,
left: 50,
width: 100,
height: 100,
fill: "green"
}))
.add(new fabric.Rect({
top: 75,
left: 75,
width: 100,
height: 100,
fill: "blue"
}))
.renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
As you can see, trying to select the red rectangle from within the blue one is not working. I'm only able to select the green or the blue. I guess that after the first doubleclick worked (green is selected), clicking again just selects blue so the following doubleclick is only able to get green again.
Is there a way around this? Any other ideas?
After some time I finally was able to solve that by myself. Clicking on an object brings it to the top. On double-clicking I try to get the object one layer behind the current object. On another dblclick I get the one behind and so on. Works great for me and also allows for the selection of fully covered objects without the need to move others.
var canvas = new fabric.Canvas("c");
canvas.on("object:selected", function (e) {
if (e.target) {
e.target.bringToFront();
this.renderAll();
}
});
var _prevActive = 0;
var _layer = 0;
//
fabric.util.addListener(canvas.upperCanvasEl, "dblclick", function (e) {
var _canvas = canvas;
//current mouse position
var _mouse = _canvas.getPointer(e);
//active object (that has been selected on click)
var _active = _canvas.getActiveObject();
//possible dblclick targets (objects that share mousepointer)
var _targets = _canvas.getObjects().filter(function (_obj) {
return _obj.containsPoint(_mouse) && !_canvas.isTargetTransparent(_obj, _mouse.x, _mouse.y);
});
_canvas.deactivateAll();
//new top layer target
if (_prevActive !== _active) {
//try to go one layer below current target
_layer = Math.max(_targets.length-2, 0);
}
//top layer target is same as before
else {
//try to go one more layer down
_layer = --_layer < 0 ? Math.max(_targets.length-2, 0) : _layer;
}
//get obj on current layer
var _obj = _targets[_layer];
if (_obj) {
_prevActive = _obj;
_obj.bringToFront();
_canvas.setActiveObject(_obj).renderAll();
}
});
//create something to play with
canvas
//fully covered rect is selectable with dblclicks
.add(new fabric.Rect({
top: 75,
left: 75,
width: 50,
height: 50,
fill: "black",
stroke: "black",
globalCompositeOperation: "xor",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 25,
left: 25,
radius: 50,
fill: "rgba(255,0,0,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 50,
left: 50,
radius: 50,
fill: "rgba(0,255,0,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 75,
left: 75,
radius: 50,
fill: "rgba(0,0,255,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
Just add one property during adding object to the canvas.
perPixelTargetFind: true
My task is a bit different - the mission is to pick the overlapping obj behind the current one:
[Left-click]+[Cmd-Key] on an selected-object to pick the first overlapping object which is right behind it.
If no overlapping objs are found, restart the search from top-layer
The idea is, for each click-event, intercept the selected object and replace it with our desired object.
A Fabric mouse-down event is like this:
User clicks on canvas
On canvas: find the target-obj by the coordinates of mouse cursor and stores it in the instance-variable (canvas._target)
Run event-handlers for mouse:down:before
Compare the target-obj found from step(2) with current selected object, fire selection:cleared/update/create events according to the results of comparison.
Set new activeObject(s)
Run event-handlers for mouse:down
We can use a customized event handler on mouse:down:before to intercept the target-obj found on Step(2) and replace it by our desired-object
fCanvas = new fabric.Canvas('my-canvas', {
backgroundColor: '#cbf1f1',
width: 800,
height: 600,
preserveObjectStacking: true
})
const r1 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 0, left: 0, fill:'red'})
const r2 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 50, left: 50, fill:'green'})
const r3 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 100, left: 100, fill:'yellow'})
fCanvas.add(r1, r2, r3)
fCanvas.requestRenderAll()
fCanvas.on('mouse:down:before', ev => {
if (!ev.e.metaKey) {
return
}
// Prevent conflicts with multi-selection
if (ev.e[fCanvas.altSelectionKey]) {
return
}
const currActiveObj = fCanvas.getActiveObject()
if (!currActiveObj) {
return
}
const pointer = fCanvas.getPointer(ev, true)
const hitObj = fCanvas._searchPossibleTargets([currActiveObj], pointer)
if (!hitObj) {
return
}
let excludeObjs = []
if (currActiveObj instanceof fabric.Group) {
currActiveObj._objects.forEach(x => { excludeObjs.push(x) })
} else {
// Target is single active object
excludeObjs.push(currActiveObj)
}
let remain = excludeObjs.length
let objsToSearch = []
let lastIdx = -1
const canvasObjs = fCanvas._objects
for (let i = canvasObjs.length-1; i >=0 ; i--) {
if (remain === 0) {
lastIdx = i
break
}
const obj = canvasObjs[i]
if (excludeObjs.includes(obj)) {
remain -= 1
} else {
objsToSearch.push(obj)
}
}
const headObjs = canvasObjs.slice(0, lastIdx+1)
objsToSearch = objsToSearch.reverse().concat(headObjs)
const found = fCanvas._searchPossibleTargets(objsToSearch, pointer)
if (found) {
fCanvas._target = found
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.min.js"></script>
<html>
<h4>Left-click + Cmd-key on overlapping area to pick the obj which is behind current one</h4>
<canvas id="my-canvas"></canvas>
</html>

Categories