Reputation: 193
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 help from Move object within canvas boundary limit for achieving this. But when I start to scale the object, it simply keeps on going out of boundary. I do not understand what has to be done to keep it inside the boundary only, even while scaling. Please help me with a code to prevent this behavior. It would be great if you can attach a demo too.
<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>
My demo is attached here. : https://jsfiddle.net/3v0cLaLk/
Upvotes: 9
Views: 17813
Reputation: 113
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;
}
})
Upvotes: 0
Reputation: 11632
I was able to block movement outside of boundaries using the Bounding box in the following way using the last version of 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;
}
Any additional extensions for Scaling objects are welcome.
Upvotes: 0
Reputation: 1
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();
});
Upvotes: 0
Reputation: 211
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>
Upvotes: 21
Reputation: 339
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
};
}
}
Upvotes: 7
Reputation: 4420
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();
}
});
Upvotes: 19