【问题标题】:How to select covered objects via mouse in fabricJS?如何在fabricJS中通过鼠标选择覆盖的对象?
【发布时间】:2016-12-30 08:38:42
【问题描述】:

我正在尝试开发一种方法来选择在下面分层并且(完全)被其他对象覆盖的对象。一种想法是选择顶部对象,然后通过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 中选择red 矩形不起作用。我只能选择greenblue。我猜想在第一个doubleclick 工作后(green 被选中),再次点击只会选择blue,所以下面的双击只能再次获得green

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

【问题讨论】:

  • “还有其他想法吗?” 当然,如果对多层矩形的 FabricJS 命中测试不可用,只需遍历矩形并在数学上测试 mouseX, mouseY 在任何矩形内。请参阅此 Html5-Canvas Documentation Example。这样你就不需要尝试协调点击&双击&三次点击等;-)
  • @markE:这不能解决我的问题。我已经知道鼠标指针击中了哪些对象(这些是我上面代码中的_targets)。我需要一种机制来select 被其他人覆盖的对象。和下面的,等等。
  • 我不是 FabricJS 大师,但你不能用fabric.Group 选择一组对象吗? var group = new fabric.Group(); group.addWithUpdate(targetObject); canvas.setActiveObject(group); canvas.add(group);

标签: javascript canvas html5-canvas selection fabricjs


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    我的任务有点不同——任务是挑选当前对象后面的重叠 obj:

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

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

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

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

    我们可以使用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>

    【讨论】:

      【解决方案3】:

      在向画布添加对象的过程中只需添加一个属性。

      perPixelTargetFind: true  
      

      【讨论】:

        猜你喜欢
        • 2021-02-01
        • 2019-06-01
        • 2012-08-01
        • 2016-11-04
        • 1970-01-01
        • 1970-01-01
        • 2020-12-21
        • 2019-10-03
        • 1970-01-01
        相关资源
        最近更新 更多