Drag And Drop Image to Canvas (FabricJS) - javascript

The Problem
I want to do this with an image instead of a canvas object. Meaning you have to add the thing you want to add TO AND AS A PART of the canvas before you can add it. The images are actually part of the website so it doesn't need to do some intricate stuff. This code I found here only works for when it's an object not an actual element. And by the way I'm using FabricJS just to let you know that I'm not using the default HTML5 canvas stuff.
As for any alternatives that are possibly going to work without using my current code. Please do post it down below in the comments or in the answers. I would really love to see what you guys got in mind.
Summary
Basically I want to be able to drag and drop images through the canvas while retaining the mouse cursor position. For example if I drag and image and the cursor was at x: 50 y: 75 it would drop the image to that exact spot. Just like what the code I found does. But like the problem stated the CODE uses an object for you to drag it to the canvas then it clones it. I want this functionality using just plain old elements. E.g: <img>.
THE CODE -
JsFiddle
window.canvas = new fabric.Canvas('fabriccanvas');
var edgedetection = 8; //pixels to snap
canvas.selection = false;
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.setHeight(window.innerHeight);
canvas.setWidth(window.innerWidth);
canvas.renderAll();
}
// resize on init
resizeCanvas();
//Initialize Everything
init();
function init(top, left, width, height, fill) {
var bg = new fabric.Rect({
left: 0,
top: 0,
fill: "#eee",
width: window.innerWidth,
height: 75,
lockRotation: true,
maxHeight: document.getElementById("fabriccanvas").height,
maxWidth: document.getElementById("fabriccanvas").width,
selectable: false,
});
var squareBtn = new fabric.Rect({
top: 10,
left: 18,
width: 40,
height: 40,
fill: '#af3',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
});
var circleBtn = new fabric.Circle({
radius: 20,
fill: '#f55',
top: 10,
left: 105,
});
var triangleBtn = new fabric.Triangle({
width: 40,
height: 35,
fill: 'blue',
top: 15,
left: 190,
});
var sqrText = new fabric.IText("Add Square", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 6,
top: 50,
selectable: false,
});
var cirText = new fabric.IText("Add Circle", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 95,
top: 50,
selectable: false,
});
var triText = new fabric.IText("Add Triangle", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 175,
top: 50,
selectable: false,
});
var shadow = {
color: 'rgba(0,0,0,0.6)',
blur: 3,
offsetX: 0,
offsetY: 2,
opacity: 0.6,
fillShadow: true,
strokeShadow: true
};
window.canvas.add(bg);
bg.setShadow(shadow);
window.canvas.add(squareBtn);
window.canvas.add(circleBtn);
window.canvas.add(triangleBtn);
window.canvas.add(sqrText);
window.canvas.add(cirText);
window.canvas.add(triText);
canvas.forEachObject(function (e) {
e.hasControls = e.hasBorders = false; //remove borders/controls
});
function draggable(object) {
object.on('mousedown', function() {
var temp = this.clone();
temp.set({
hasControls: false,
hasBorders: false,
});
canvas.add(temp);
draggable(temp);
});
object.on('mouseup', function() {
// Remove an event handler
this.off('mousedown');
// Comment this will let the clone object able to be removed by drag it to menu bar
// this.off('mouseup');
// Remove the object if its position is in menu bar
if(this.top<=75) {
canvas.remove(this);
}
});
}
draggable(squareBtn);
draggable(circleBtn);
draggable(triangleBtn);
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
canvas.forEachObject(function (targ) {
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
activeObject === targ
}
});
});
}
More codes that I found but doesn't answer my problem:
var canvas = new fabric.Canvas('c');
canvas.on("after:render", function(){canvas.calcOffset();});
var started = false;
var x = 0;
var y = 0;
var width = 0;
var height = 0;
canvas.on('mouse:down', function(options) {
//console.log(options.e.clientX, options.e.clientY);
x = options.e.clientX;
y = options.e.clientY;
canvas.on('mouse:up', function(options) {
width = options.e.clientX - x;
height = options.e.clientY - y;
var square = new fabric.Rect({
width: width,
height: height,
left: x + width/2 - canvas._offset.left,
top: y + height/2 - canvas._offset.top,
fill: 'red',
opacity: .2
});
canvas.add(square);
canvas.off('mouse:up');
$('#list').append('<p>Test</p>');
});
});
This code adds a rectangle to the canvas. But the problem is this doesn't achieve what I want which is basically, as previously stated, that I want to be able to drag an img element and then wherever you drag that image on the canvas it will drop it precisely at that location.
CREDITS
Fabric JS: Copy/paste object on mouse down
fabric.js Offset Solution

it doesn't need to do some intricate stuff.
A logical framework under which to implement image dragging might be to monitor mouse events using event listeners.
On mouse down over an Image element within the page but not over the canvas, record which image element and the mouse position within the image. On mouse up anywhere set this record back to null.
On mouse over of the canvas with a non empty image record, create a Fabric image element from the Image element (as per documentation), calculate where it goes from the recorded image position and current mouse position, paste it under the mouse, make it draggable and simulate the effect of a mouse click to continue dragging it.
I have taken this question to be about the design and feasibility stages of program development.

Related

Basic Fabric.js Image Selection with Mask

I have a unique (but hopefully simple) issue to fix with Fabric.js to fix.
I have this very simple example below:
I have
2 Images
Both have an absolutePositioned mask via clipPath property on the fabric.Image instance
My issue is:
I want the image to only be selectable (and hoverable) whenever the selection happens within the bounds of the mask, not anywhere on image (even outside of the bounds of its mask).
This image shows the mouse hovering over the red door picture (even though the mouse is outside of the mask bounds, but not outside the image bounds:
Here's a code snippet of the door image snippet:
fabric.Image.fromURL(url1, function(img){
canvas.add(img.set({
left: 0,
top: 0,
clipPath: rect1,
hasControls: false,
}));
img.on('mouseover', () => {
const filter = new fabric.Image.filters.BlendColor({
color: 'white',
alpha: 0.7,
mode: 'tint'
})
img.filters.push(filter)
img.applyFilters()
canvas.renderAll()
})
img.on('mouseout', () => {
img.filters.pop()
img.applyFilters()
canvas.renderAll()
})
}, {crossOrigin: "Anonymous"});
JS Fiddle Example showing the current behavior that I'm trying to change.
A possible solution is to listen to the mouse event in the canvas and toggle the activeObject based on the mouse position:
canvas.observe('mouse:move', function(options) {
const pos = canvas.getPointer(options.e);
if (!imageRight || !imageLeft) return
if (pos.x > 200) {
activeImage = imageRight
} else {
activeImage = imageLeft
}
const activeObj = canvas.getActiveObject();
if (activeImage !== activeObj) {
canvas.setActiveObject(activeImage);
canvas.renderAll()
}
});
This is a very simplified way to detect if the mouse is over the image. It checks if the x > 200 since that is where the line between both images is positioned. This could be improved to be more accurate, but it works just to illustrate the idea.
var canvas = window._canvas = new fabric.Canvas('c');
const url1 = 'https://picsum.photos/300/200'
const url2 = 'https://picsum.photos/320'
let imageRight
let imageLeft
let activeImage
canvas.observe('mouse:move', function(options) {
const pos = canvas.getPointer(options.e);
if (!imageRight || !imageLeft) return
if (pos.x > 200) {
activeImage = imageRight
} else {
activeImage = imageLeft
}
const activeObj = canvas.getActiveObject();
if (activeImage !== activeObj) {
canvas.setActiveObject(activeImage);
canvas.renderAll()
}
});
const baseProps = {
width: 200,
height: 200,
top: 0,
fill: '#000',
absolutePositioned: true,
hasControls: false,
evented: false,
selectable: false
}
const rect1 = new fabric.Rect({
...baseProps,
left: 0,
})
const rect2 = new fabric.Rect({
...baseProps,
left: 200,
})
canvas.add(rect1)
canvas.add(rect2)
fabric.Image.fromURL(url2, function(img) {
imageRight = img
canvas.add(img.set({
left: 200,
top: 0,
clipPath: rect2,
hasControls: false
}))
}, {
crossOrigin: "Anonymouse"
})
fabric.Image.fromURL(url1, function(img) {
imageLeft = img
canvas.add(img.set({
left: 0,
top: 0,
clipPath: rect1,
hasControls: false,
}));
}, {
crossOrigin: "Anonymous"
});
canvas {
border: 2px red solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.6.0/fabric.min.js"></script>
<canvas id="c" width="420" height="220"></canvas>

Can't get overlay to resize fabric.js?

I've been able to get the canvas to resize, and also include touch events. But for some reason, I can't get the overlay image to fill the canvas.
I'm using the recommended code from fabric.js
canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
width: width,
height: height,
// Needed to position overlayImage at 0/0
originX: 'left',
originY: 'top'
});
My Fiddle has a fair bit of code, as I'm not sure if it's the resizer or touch events that might be causing this issue.
The problem in your fiddle stems from the fact that in Fabric.js, setting width and height don't scale an image to the given values but rather set the dimensions of the fabric.Image container. What you need to do is call canvas.overlayImage.scaleToWidth(width) (or .scaleToHeight(height)) in the setOverlayImage()'s callback, so that the image would actually be scaled to cover the whole canvas:
//Image Edit
var canvas = new fabric.Canvas('c', {
preserveObjectStacking: true
});
canvas.setWidth(window.innerWidth);
canvas.setHeight(window.innerWidth);
fabric.Image.fromURL("https://4.img-dpreview.com/files/p/E~TS590x0~articles/3925134721/0266554465.jpeg", function(img) {
img.scale(0.5).set({
left: 150,
top: 150,
angle: 0
});
canvas.add(img).setActiveObject(img);
});
var info = document.getElementById('info');
function resize() {
var canvasSizer = document.getElementById("imageEditor");
var canvasScaleFactor = canvasSizer.offsetWidth / 525;
var width = canvasSizer.offsetWidth;
var height = canvasSizer.offsetHeight;
var ratio = canvas.getWidth() / canvas.getHeight();
if ((width / height) > ratio) {
width = height * ratio;
} else {
height = width / ratio;
}
var scale = width / canvas.getWidth();
var zoom = canvas.getZoom();
zoom *= scale;
canvas.setDimensions({
width: width,
height: height
});
canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0])
canvas.setOverlayImage('https://i.imgur.com/1CURvP5.png', function() {
canvas.overlayImage && canvas.overlayImage.scaleToWidth(width)
canvas.renderAll()
}, {
// Needed to position overlayImage at 0/0
originX: 'left',
originY: 'top'
});
}
window.addEventListener('load', resize, false);
window.addEventListener('resize', resize, false);
var textObj = new fabric.IText("Test Text", {
fontSize: 22,
top: 362.5,
left: 262.5,
hasControls: true,
fontWeight: 'bold',
fontFamily: 'Montserrat',
fontStyle: 'normal',
centeredrotation: true,
originX: 'center',
originY: 'center'
});
canvas.add(textObj);
canvas.renderAll();
canvas.on({
'touch:gesture': function() {
var text = document.createTextNode(' Gesture ');
info.insertBefore(text, info.firstChild);
},
'touch:drag': function() {
var text = document.createTextNode(' Dragging ');
info.insertBefore(text, info.firstChild);
},
'touch:orientation': function() {
var text = document.createTextNode(' Orientation ');
info.insertBefore(text, info.firstChild);
},
'touch:shake': function() {
var text = document.createTextNode(' Shaking ');
info.insertBefore(text, info.firstChild);
},
'touch:longpress': function() {
var text = document.createTextNode(' Longpress ');
info.insertBefore(text, info.firstChild);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.4/fabric.js"></script>
<div id="imageEditor">
<div class="canvas-container">
<canvas id="c"></canvas>
</div>
</div>
(I had to re-upload the jail_cell_bars.png image elsewhere because the original hosting was not cooperating)

FabricJS FlipX object

I am having trouble flipping or mirroring the object horizontally when the object itself is clicked on the FabricJS canvas.
I came close but it was mirroring the object when it was being resized too, which I didn't want.
I would guess I need to add the 'flipX: true' attribute to object on the first click and on the next click remove that attribute and so on with each click. Or maybe that is over complicating it and it can be done much easier with a flipX function I do not know.
I did find a Fiddle that flipped the object, but it was onclick of a button not the object itself.
I am struggling to solve this :\
My Fiddle
HTML:
<canvas id="canvas" width="400" height="300"></canvas>
JS:
var canvas = this.__canvas = new fabric.Canvas('canvas');
canvas.on('object:selected', function() {
toggle('flipX');
});
// create a rectangle
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 50,
angle: 20,
fill: 'red'
});
canvas.add(rect);
canvas.renderAll();
You could accomplish that in the following way ...
var canvas = this.__canvas = new fabric.Canvas('canvas');
// mouse event
canvas.on('mouse:down', function(e) {
if (e.target) {
if (!e.target.__corner) {
e.target.toggle('flipX');
canvas.renderAll();
}
e.target.__corner = null;
}
});
// create a rectangle
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 50,
angle: 20,
});
// set gradient (for demonstration)
rect.setGradient('fill', {
type: 'linear',
x1: -rect.width / 2,
y1: 0,
x2: rect.width / 2,
y2: 0,
colorStops: {
0: '#ffe47b',
1: 'rgb(111,154,211)'
}
});
canvas.add(rect);
rect.set('flipX', true);
canvas.renderAll();
body{margin:10px 0 0 0;overflow:hidden}canvas{border:1px solid #ccc}
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="canvas" width="208" height="208"></canvas>
Same from above, different case.
Apply flip to background image
fabric.Image.fromURL('../' +ImageUrl, function (img02) {
Backcanvas.setBackgroundImage(img02, Backcanvas.renderAll.bind(Backcanvas), {
backgroundImageStretch: false,
top: 0,
left: 0,
originX: 'left',
originY: 'top',
flipY:'true'
});
Backcanvas.renderAll();
Backcanvas.backgroundImage.setCoords();
canvas.renderAll();
Backcanvas.renderAll();
}, { crossOrigin: 'anonymous' });

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