Prevent Fabric js Objects from scaling out of the canvas boundary

前端 未结 4 1481
[愿得一人]
[愿得一人] 2020-12-06 07:46

I have been trying to keep an object (constructed in fabric js over a canvas) inside the boundaries at all the times. It has been achieved at moving and rotating it. I took

相关标签:
4条回答
  • 2020-12-06 08:22

    You can set on object modified listener and check if object is out of bounds. If so, then restore it to its original state.

    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();
        }
    });
    
    0 讨论(0)
  • 2020-12-06 08:24

    Below is the code for blocking the coordinates of any object outside the canvas area from all directions

    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();
    });
    
    0 讨论(0)
  • 2020-12-06 08:33

    I was able to solve the problem as follows:

    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>

    0 讨论(0)
  • 2020-12-06 08:38

    If you want to perform a real time prevention, you should use object:scaling event, as object:modified is only triggered at the end of the transformation.

    1) Add event handler to canvas:

    this.canvas.on('object:scaling', (e) => this._handleScaling(e));
    

    2) In the handler function, get the old and the new object's bounding rect:

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

    3) For each border, check if object has scaled beyond the canvas boundaries and compute its left, top and scale properties:

      // 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) Similar code for the other borders:

      // 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) If object's BoundingRect has crossed canvas boundaries, fix its position and scale:

      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) Finally, when setting the scaling properties, we have to stick with the smallest scale in case the object has crossed more than one border:

    _setScalingProperties(left, top, scale) {
      if(this.scalingProperties == null 
      || this.scalingProperties['scale'] > scale) {
        this.scalingProperties = {
          'left': left,
          'top': top,
          'scale': scale
        };
      }
    }
    
    0 讨论(0)
提交回复
热议问题