Fabricjs - 仅通过边框选择

Fabricjs - selection only via border

我正在使用 Fabric.js 在 canvas 上绘制一些矩形。默认行为是在矩形内部单击选择它。如何更改行为,使其仅在单击矩形边框时才被选中?

在矩形内部而不是边框​​上单击应该不会执行任何操作。

您可以通过在 TradingView.com 图表上绘制一个矩形来查看此行为

在 fabric 中有一个选项,如果没有,我该如何实施它?

这是我的方法,当单击矩形时,我正在计算它被单击的位置并且 如果没有点击边框,我必须设置 canvas.discardActiveObject ,请参阅代码

的注释

var canvas = new fabric.Canvas('c', {
  selection: false
});
var rect = new fabric.Rect({
  left: 50,
  top: 50,
  width: 100,
  height: 100,
  strokeWidth: 10,
  stroke: 'red',
  selectable: false,
  evented: true,
  hasBorders: true,
  lockMovementY: true,
  lockMovementX: true

})
canvas.on("mouse:move", function(e) {
  if (!e.target || e.target.type != 'rect') return;
  // when selected event is fired get the click position.
  var pointer = canvas.getPointer(e.e);
  // calculate the click distance from object to be exact
  var distanceX = pointer.x - rect.left;
  var distanceY = pointer.y - rect.top;

  // check if click distanceX/Y are less than 10 (strokeWidth) or greater than 90 ( rect width = 100)


  if ((distanceX <= rect.strokeWidth || distanceX >= (rect.width - rect.strokeWidth)) || (distanceY <= rect.strokeWidth || distanceY >= (rect.height - rect.strokeWidth))) {
    rect.set({
      hoverCursor: 'move',
      selectable: true,
      lockMovementY: false,
      lockMovementX: false
    });
    document.getElementById('result').innerHTML = 'on border';
  } else {
    canvas.discardActiveObject();
    document.getElementById('result').innerHTML = 'not  on border';
    rect.set({
      hoverCursor: 'default',
      selectable: false,
      lockMovementY: true,
      lockMovementX: true
    });
  }

});

canvas.add(rect);
canvas.renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<div id="result" style="width: 100%; "></div>
<canvas id="c" width="600" height="200"></canvas>
<pre>
</pre>

ps:你也可以设置rect属性为selectable: false并在if语句中调用canvas.setActiveObject(this);使其选择。

Fabric.js 使用 Object.containsPoint() 来确定鼠标事件是否应该指向对象。此方法反过来通过 Object._getImageLines() 计算对象的边缘,并检查鼠标指针的投影穿过这些线的次数。

下面的解决方案根据每个角的坐标计算额外的内边,因此自动处理对象缩放和旋转。

const canvas = new fabric.Canvas('c', {
  enableRetinaScaling: true
})

const rect = new fabric.Rect({
  left: 0,
  top: 0,
  width: 100,
  height: 100,
  dragBorderWidth: 15, // this is the custom attribute we've introduced
})

function innerCornerPoint(start, end, offset) {
  // vector length
  const l = start.distanceFrom(end)
  // unit vector
  const uv = new fabric.Point((end.x - start.x) / l, (end.y - start.y) / l)
  // point on the vector at a given offset but no further than side length
  const p = start.add(uv.multiply(Math.min(offset, l)))
  // rotate point
  return fabric.util.rotatePoint(p, start, fabric.util.degreesToRadians(45))
}

rect._getInnerBorderLines = function(c) {
  // the actual offset from outer corner is the length of a hypotenuse of a right triangle with border widths as 2 sides
  const offset = Math.sqrt(2 * (this.dragBorderWidth ** 2))
  // find 4 inner corners as offsets rotated 45 degrees CW
  const newCoords = {
    tl: innerCornerPoint(c.tl, c.tr, offset),
    tr: innerCornerPoint(c.tr, c.br, offset),
    br: innerCornerPoint(c.br, c.bl, offset),
    bl: innerCornerPoint(c.bl, c.tl, offset),
  }
  return this._getImageLines(newCoords)
}

rect.containsPoint = function(point, lines, absolute, calculate) {
  const coords = calculate ? this.calcCoords(absolute) : absolute ? this.aCoords : this.oCoords
  lines = lines || this._getImageLines(coords)
  const innerRectPoints = this._findCrossPoints(point, lines);
  const innerBorderPoints = this._findCrossPoints(point, this._getInnerBorderLines(coords))
  // calculate intersections
  return innerRectPoints === 1 && innerBorderPoints !== 1
}

canvas.add(rect)
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="c" width="400" height="300"></canvas>

此方法会覆盖 FabricJS 中的 _checkTarget 方法,以拒绝距边界超过指定距离(由 clickableMargin 变量定义)的点击。

//sets the width of clickable area
var clickableMargin = 15;

var canvas = new fabric.Canvas("canvas");

canvas.add(new fabric.Rect({
  width: 150,
  height: 150,
  left: 25,
  top: 25,
  fill: 'green',
  strokeWidth: 0
}));

//overrides the _checkTarget method to add check if point is close to the border
fabric.Canvas.prototype._checkTarget = function(pointer, obj, globalPointer) {
  if (obj &&
      obj.visible &&
      obj.evented &&
      this.containsPoint(null, obj, pointer)){
    if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
      var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y);
      if (!isTransparent) {
        return true;
      }
    }
    else {
     var isInsideBorder = this.isInsideBorder(obj);
     if(!isInsideBorder) {
       return true;
      }
    }
  }
}

fabric.Canvas.prototype.isInsideBorder = function(target) {
   var pointerCoords = target.getLocalPointer();
   if(pointerCoords.x > clickableMargin && 
     pointerCoords.x < target.getScaledWidth() - clickableMargin && 
     pointerCoords.y > clickableMargin && 
     pointerCoords.y < target.getScaledHeight() - clickableMargin) {
     return true;
   }
 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="canvas" height="300" width="400"></canvas>