Fidel90 Fidel90 - 3 months ago 62
Javascript Question

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?

Answer

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>