防止Fabric js对象缩放超出画布边界。

9
我一直在尝试让一个对象(在画布上使用fabric js构建)始终保持在边界内。在移动和旋转它时已经实现了这一点。我参考了Move object within canvas boundary limit来实现这一点。但是,当我开始缩放对象时,它就会简单地超出边界。我不明白如何才能使它在缩放时仍然保持在边界内。请帮我编写代码以防止这种行为,并附上演示。
    <html>
<head>
    <title>Basic usage</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.3/fabric.min.js"></script>

</head>
<body>
<canvas id="canvas" style= "border: 1px solid black" height= 480 width = 360></canvas>
<script>
 var canvas = new fabric.Canvas('canvas');
  canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));

  canvas.item(0).set({
    borderColor: 'gray',
    cornerColor: 'black',
    cornerSize: 12,
    transparentCorners: true
  });
  canvas.setActiveObject(canvas.item(0));
  canvas.renderAll();


  canvas.on('object:moving', function (e) {
        var obj = e.target;
         // if object is too big ignore
        if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
            return;
        }        
        obj.setCoords();        
        // top-left  corner
        if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
            obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
            obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
        }
        // bot-right corner
        if(obj.getBoundingRect().top+obj.getBoundingRect().height  > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width  > obj.canvas.width){
            obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
            obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
        }
});

</script>
</body>
</html>

此处附上我的演示代码: https://jsfiddle.net/3v0cLaLk/

(注:演示代码是一个网址链接)


你看过下面的解决方案“在画布边界限制内移动对象”吗?https://dev59.com/q2Ah5IYBdhLWcg3wBPbY#36011859 - Tim Harker
是的 Tim,我尝试了这个解决方案,但似乎并没有起作用。一旦你的对象被拉伸超出边界,它就失控了。 - Ankit Joshi
你能进一步解释一下你所说的“它失控了”是什么意思吗?你是指它超出了画布范围吗? - Tim Harker
谢谢Tim的回复。是的。它只会不断地扩展,然后如果你离开鼠标,你就看不到端点了,无法将其收回。在fiddle中尝试一下。让一侧接触边界,从另一侧增加大小。让它超出边界。也许那时你就会明白发生了什么。 - Ankit Joshi
@AnkitJoshi,你为什么需要使用Math.max?例如:obj.top = obj.top - obj.getBoudingRect().top不就足够了吗?我找不到一个这样的情况,即Math.max(obj.top, obj.top-obj.getBoundingRect().top);obj.top会被视为obj.getBoundingRect().top始终为负数。 - broadband
@broaband obj.top会给我形状的最高点。另一方面,obj.getBoundingRect().top总是会给我旋转手柄下方的点的坐标。想象一下,如果我将形状旋转180度,那么boundingRect.top就会在下面。因此,我要检查这两者的最大值。 - Ankit Joshi
6个回答

20

我按照以下方式解决了该问题:

var canvas = new fabric.Canvas('canvas');
  canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));

  canvas.item(0).set({
    borderColor: 'gray',
    cornerColor: 'black',
    cornerSize: 12,
    transparentCorners: true
  });
  canvas.setActiveObject(canvas.item(0));
  canvas.renderAll();


  canvas.on('object:moving', function (e) {
        var obj = e.target;
         // if object is too big ignore
        if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
            return;
        }        
        obj.setCoords();        
        // top-left  corner
        if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
            obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
            obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
        }
        // bot-right corner
        if(obj.getBoundingRect().top+obj.getBoundingRect().height  > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width  > obj.canvas.width){
            obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
            obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
        }
});

    var left1 = 0;
    var top1 = 0 ;
    var scale1x = 0 ;    
    var scale1y = 0 ;    
    var width1 = 0 ;    
    var height1 = 0 ;
  canvas.on('object:scaling', function (e){
    var obj = e.target;
    obj.setCoords();
    var brNew = obj.getBoundingRect();
    
    if (((brNew.width+brNew.left)>=obj.canvas.width) || ((brNew.height+brNew.top)>=obj.canvas.height) || ((brNew.left<0) || (brNew.top<0))) {
    obj.left = left1;
    obj.top=top1;
    obj.scaleX=scale1x;
    obj.scaleY=scale1y;
    obj.width=width1;
    obj.height=height1;
  }
    else{    
      left1 =obj.left;
      top1 =obj.top;
      scale1x = obj.scaleX;
      scale1y=obj.scaleY;
      width1=obj.width;
      height1=obj.height;
    }
 });
<html>
<head>
    <title>Basic usage</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.3/fabric.min.js"></script>

</head>
<body>
<canvas id="canvas" style= "border: 1px solid black" height= 480 width = 360></canvas>
</body>
</html>


19
您可以设置一个对象修改的监听器,检查对象是否超出边界,如果是,则将其恢复到原始状态。
this.canvas.on('object:modified', function (options: any) {
    let obj = options.target;
    let boundingRect = obj.getBoundingRect(true);
    if (boundingRect.left < 0
        || boundingRect.top < 0
        || boundingRect.left + boundingRect.width > scope.canvas.getWidth()
        || boundingRect.top + boundingRect.height > scope.canvas.getHeight()) {
        obj.top = obj._stateProperties.top;
        obj.left = obj._stateProperties.left;
        obj.angle = obj._stateProperties.angle;
        obj.scaleX = obj._stateProperties.scaleX;
        obj.scaleY = obj._stateProperties.scaleY;
        obj.setCoords();
        obj.saveState();
    }
});

谢谢。这个对我的需求来说只需要稍微改动一下就可以了。非常感谢你的帮助。谢谢,伙计。 - Ankit Joshi
1
需要注意的是,canvas 应该使用 stateful = true 选项构建,否则对象中将没有 _stateProperties 属性。 - Victor
我希望在运行时而不是在最后检查它,我该如何实现这一点?使用您提供的现有代码作为答案中提到的方法? - Sundeep Pidugu

7
如果你想实时预防某些情况发生,你应该使用object:scaling事件,因为object:modified只在变换结束时触发。
1)向画布添加事件处理程序:
this.canvas.on('object:scaling', (e) => this._handleScaling(e));

2) 在处理函数中,获取旧对象和新对象的边界矩形:

_handleScaling(e) {
  var obj = e.target;
  var brOld = obj.getBoundingRect();
  obj.setCoords();
  var brNew = obj.getBoundingRect();

3)对于每个边框,检查对象是否已经缩放超出画布边界,并计算其左侧、顶部和缩放属性:

  // left border
  // 1. compute the scale that sets obj.left equal 0
  // 2. compute height if the same scale is applied to Y (we do not allow non-uniform scaling)
  // 3. compute obj.top based on new height
  if(brOld.left >= 0 && brNew.left < 0) {
    let scale = (brOld.width + brOld.left) / obj.width;
    let height = obj.height * scale;
    let top = ((brNew.top - brOld.top) / (brNew.height - brOld.height) *
      (height - brOld.height)) + brOld.top;
    this._setScalingProperties(0, top, scale);
  } 

4) 其他边界的代码类似:

  // top border
  if(brOld.top >= 0 && brNew.top < 0) {
    let scale = (brOld.height + brOld.top) / obj.height;
    let width = obj.width * scale;
    let left = ((brNew.left - brOld.left) / (brNew.width - brOld.width) * 
      (width - brOld.width)) + brOld.left;
    this._setScalingProperties(left, 0, scale);
  }
  // right border
  if(brOld.left + brOld.width <= obj.canvas.width 
  && brNew.left + brNew.width > obj.canvas.width) {
    let scale = (obj.canvas.width - brOld.left) / obj.width;
    let height = obj.height * scale;
    let top = ((brNew.top - brOld.top) / (brNew.height - brOld.height) * 
      (height - brOld.height)) + brOld.top;
    this._setScalingProperties(brNew.left, top, scale);
  }
  // bottom border
  if(brOld.top + brOld.height <= obj.canvas.height 
  && brNew.top + brNew.height > obj.canvas.height) {
    let scale = (obj.canvas.height - brOld.top) / obj.height;
    let width = obj.width * scale;
    let left = ((brNew.left - brOld.left) / (brNew.width - brOld.width) * 
      (width - brOld.width)) + brOld.left;
    this._setScalingProperties(left, brNew.top, scale);
  }

5) 如果对象的BoundingRect已经越过了画布的边界,则修正其位置和比例:

  if(brNew.left < 0
  || brNew.top < 0
  || brNew.left + brNew.width > obj.canvas.width
  || brNew.top + brNew.height > obj.canvas.height) {
    obj.left = this.scalingProperties['left'];
    obj.top = this.scalingProperties['top'];
    obj.scaleX = this.scalingProperties['scale'];
    obj.scaleY = this.scalingProperties['scale'];
    obj.setCoords();
  } else {
    this.scalingProperties = null;
  }
}

6) 最后,在设置缩放属性时,如果对象跨越了多个边界,我们必须坚持使用最小的比例:

_setScalingProperties(left, top, scale) {
  if(this.scalingProperties == null 
  || this.scalingProperties['scale'] > scale) {
    this.scalingProperties = {
      'left': left,
      'top': top,
      'scale': scale
    };
  }
}

当我实现它时,它会生成错误“属性'scalingProperties'不存在”@William Dias - kunal shaktawat
@kunalshaktawat,自从我发布答案以来,API可能已经发生了变化。请检查文档以使用正确的方法和属性。 - William Dias
1
@AmanGojariya,在旋转时,您将需要为旋转事件创建一个处理程序。可能,事件数据与缩放事件提供的数据不同。您必须根据自己的需求调整代码。 - William Dias
@Marcel,这段代码没有在更新版本的Fabric.js上进行测试。也许这就是你面临问题的原因。 - William Dias
这适用于不规则的外形并防止内部形状超出外部形状的边界框,还是仅适用于外部形状的边界框? - Shashank Bhatt
显示剩余2条评论

0
我可以使用 Fabric 的最新版本(“fabric”:“^4.6.0”)和 TypeScript,通过以下方式来使用边界框阻止移动超出边界:
private boundingBox: fabric.Rect = null;

this.setBoundingBox(width, height);

private setBoundingBox(width: number, height: number) {
        this.boundingBox = new fabric.Rect({
            name: OBJECT_TYPE.BOUNDING_BOX,
            fill: DEFINITIONS.BG_COLOR,
            width: width,
            height: height,
            hasBorders: false,
            hasControls: false,
            lockMovementX: true,
            lockMovementY: true,
            selectable: false,
            evented: false,
            stroke: 'red',
        });
        this._canvas.add(this.boundingBox);
    }

this._canvas.on('object:moving', (e) => {
            console.log('object:moving');
            this._avoidObjectMovingOutsideOfBoundaries(e);
        });

private _avoidObjectMovingOutsideOfBoundaries(e: IEvent) {
        let obj = e.target;
        const top = obj.top;
        const bottom = top + obj.height;
        const left = obj.left;
        const right = left + obj.width;

        const topBound = this.boundingBox.top;
        const bottomBound = topBound + this.boundingBox.height;
        const leftBound = this.boundingBox.left;
        const rightBound = leftBound + this.boundingBox.width;

        obj.left = Math.min(Math.max(left, leftBound), rightBound - obj.width);
        obj.top = Math.min(Math.max(top, topBound), bottomBound - obj.height);

        return obj;
    }

欢迎为缩放对象添加任何额外的扩展。


0
 canvas.on('object:scaling', function (e) {
      var obj = e.target;
      obj.setCoords();
      let top = obj.getBoundingRect().top;
      let left = obj.getBoundingRect().left;
      let height = obj.getBoundingRect().height;
      let width = obj.getBoundingRect().width;

      // restrict scaling below bottom of canvas
      if (top + height > CANVAS_HEIGHT) {
        obj.scaleY = 1;
        obj.setCoords();
        let h = obj.getScaledHeight();

        obj.scaleY = (CANVAS_HEIGHT - top) / h;
        obj.setCoords();
        canvas.renderAll();

        obj.lockScalingX = true;
        obj.lockScalingY = true;
        obj.lockMovementX = true;
        obj.lockMovementY = true;
      }

      // restrict scaling above top of canvas
      if (top < 0) {
        obj.scaleY = 1;
        obj.setCoords();
        let h = obj.getScaledHeight();
        obj.scaleY = (height + top) / h;
        obj.top = 0;
        obj.setCoords();
        canvas.renderAll();

        obj.lockScalingX = true;
        obj.lockScalingY = true;
        obj.lockMovementX = true;
        obj.lockMovementY = true;
      }

      // restrict scaling over right of canvas
      if (left + width > CANVAS_WIDTH) {
        obj.scaleX = 1;
        obj.setCoords();
        let w = obj.getScaledWidth();

        obj.scaleX = (CANVAS_WIDTH - left) / w;
        obj.setCoords();
        canvas.renderAll();

        obj.lockScalingX = true;
        obj.lockScalingY = true;
        obj.lockMovementX = true;
        obj.lockMovementY = true;
      }

      // restrict scaling over left of canvas
      if (left < 0) {
        obj.scaleX = 1;
        obj.setCoords();
        let w = obj.getScaledWidth();
        obj.scaleX = (width + left) / w;
        obj.left = 0;
        obj.setCoords();
        canvas.renderAll();
        obj.lockScalingX = true;
        obj.lockScalingY = true;
        obj.lockMovementX = true;
        obj.lockMovementY = true;
      }
    });

 canvas.on('object:modified', function (event) {
      // after text object is done with modifing e.g. resizing or moving
      if (!!event.target) {
        event.target.lockScalingX = false;
        event.target.lockScalingY = false;
        event.target.lockMovementX = false;
        event.target.lockMovementY = false;
      }
 })

0
以下是阻止任何物体坐标超出画布范围的代码。
canvas.on('object:modified', function (data) {
var currentObject = data.target;
var tempObject = angular.copy(data.target);
var canvasMaxWidth = canvas.width - 20,
    canvasMaxHeight = canvas.height - 20;
    var actualWidth = currentObject.getBoundingRect().width,
    actualHeight = currentObject.getBoundingRect().height;
if (actualHeight > canvasMaxHeight) {
    currentObject.scaleToHeight(canvasMaxHeight);
    currentObject.setCoords();
    canvas.renderAll();
    if (tempObject.scaleX < currentObject.scaleX) {
        currentObject.scaleX = tempObject.scaleX;
        currentObject.setCoords();
        canvas.renderAll();
    }
    if (tempObject.scaleY < currentObject.scaleY) {
        currentObject.scaleY = tempObject.scaleY;
        currentObject.setCoords();
        canvas.renderAll();
    }
        if (currentObject.getBoundingRectHeight() < canvasMaxHeight - 50) {
            currentObject.scaleX = (currentObject.scaleX * canvasMaxHeight) / (currentObject.scaleX * currentObject.width);
            currentObject.setCoords();
            canvas.renderAll();
        }

}
if (actualWidth > canvasMaxWidth) {
    currentObject.scaleToWidth(canvasMaxWidth);
    obj.setCoords();
    canvas.renderAll();
    if (tempObject.scaleX < currentObject.scaleX) {
        currentObject.scaleX = tempObject.scaleX;
        currentObject.setCoords();
        canvas.renderAll();
    }
    if (tempObject.scaleY < currentObject.scaleY) {
        currentObject.scaleY = tempObject.scaleY;
        currentObject.setCoords();
        canvas.renderAll();
    }
}
obj.setCoords();
canvas.renderAll();
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接