如何在 fabricJS 中通过鼠标 select 覆盖对象?

How to select covered objects via mouse in fabricJS?

我正在尝试开发一种方法来 select 位于下方并(完全)被其他对象覆盖的对象。一个想法是 select 顶部对象,然后通过 doubleclick 向下穿过层。这是我目前得到的:

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>

如您所见,尝试从 blue 中 select red 矩形是行不通的。我只能 select greenblue。我猜想在第一个 doubleclick 工作之后(green 被 selected),再次点击 selects blue 所以下面的双击只能得到再次green

有办法解决这个问题吗?还有其他想法吗?

一段时间后,我终于能够自己解决这个问题。单击一个对象将其带到顶部。双击时,我尝试让对象在当前对象后面一层。在另一个 dblclick 上,我得到了后面的一个,依此类推。对我来说效果很好,还可以选择完全覆盖的对象,而无需移动其他对象。

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>

我的任务有点不同 - 任务是选择当前对象后面的重叠对象:

  1. 在选定对象上[左键单击]+[Cmd-键] 以拾取紧跟其后的第一个重叠对象。
  2. 如果没有找到重叠的对象,从顶层重新开始搜索

我们的想法是,对于每个点击事件,拦截所选对象并将其替换为我们想要的对象。

一个Fabric mouse-down事件是这样的:

  1. 用户点击 canvas
  2. On canvas:通过鼠标光标坐标找到target-obj并将其存储在实例变量中(canvas._target)
  3. 运行 mouse:down:before
  4. 的事件处理程序
  5. 将步骤(2)中找到的target-obj与当前选中的对象进行比较,根据比较结果触发selection:cleared/update/create事件。
  6. 设置新的活动对象
  7. 运行 mouse:down
  8. 的事件处理程序

我们可以在 mouse:down:before 上使用自定义事件处理程序来拦截在步骤 (2) 中找到的 target-obj 并将其替换为我们想要的对象

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>

在向 canvas 添加对象时只需添加一个 属性。

perPixelTargetFind: true