如何在 fabricJS 中通过鼠标选择被覆盖的对象?

问题描述 投票:0回答:3

我正在尝试开发一种方法来选择位于下方并(完全)被其他对象覆盖的对象。一个想法是选择顶部的对象,然后通过

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>

如您所见,尝试从

red
中选择
blue
矩形是行不通的。我只能选择
green
blue
。我猜想在第一个
doubleclick
工作后(选择
green
),再次单击只会选择
blue
所以下面的双击只能再次获得
green

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

javascript canvas html5-canvas selection fabricjs
3个回答
5
投票

一段时间后,我终于能够自己解决这个问题。单击一个对象将其带到顶部。双击时,我尝试让对象在当前对象后面一层。在另一个 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>


3
投票

尝试 perPixelTargetFind

第一次创建对象时,添加一个附加属性:

perPixelTargetFind: true

文档:http://fabricjs.com/docs/fabric.Object.html#perPixelTargetFind

当设置为

true
时,对象是在画布上以像素为基础“找到”的,而不是根据边界框


1
投票

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

  1. [Left-click]+[Cmd-Key] 在选定的对象上选择第一个重叠的对象,它就在它后面。
  2. 如果没有找到重叠的objs,从顶层重新开始搜索

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

A Fabric

mouse-down
活动是这样的:

  1. 用户点击画布
  2. 在画布上:通过鼠标光标的坐标找到
    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>

© www.soinside.com 2019 - 2024. All rights reserved.