防止 Fabric js 对象超出画布边界
Posted
技术标签:
【中文标题】防止 Fabric js 对象超出画布边界【英文标题】:Prevent Fabric js Objects from scaling out of the canvas boundary 【发布时间】:2017-08-07 13:43:52 【问题描述】:我一直试图将一个对象(在画布上用织物 js 构建)保持在边界内。它已在移动和旋转它时实现。我得到了Move object within canvas boundary limit 的帮助来实现这一目标。但是当我开始缩放对象时,它只是不断超出边界。我不明白必须做什么才能将其仅保持在边界内,即使在缩放时也是如此。请帮助我编写代码以防止这种行为。如果能附上demo就好了。
<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/
【问题讨论】:
你看下面的解决方案“在画布边界限制内移动对象”***.com/a/36011859/3389046? 是的蒂姆,我试过这个解决方案,但这似乎不起作用。一旦你的对象被拉伸到边界之外,它就会失去控制。 您能否进一步解释一下您所说的“它失控”是什么意思?你的意思是它走出了画布??? 感谢蒂姆的回复。是的。它只是继续扩展,然后如果您离开鼠标,则看不到端点以将其放回原处。在小提琴中尝试一下。保持一侧接触边界并从另一端增加尺寸。让它越界。大概到那时你就会明白会发生什么。 @AnkitJoshi 为什么需要Math.max
?例如:obj.top = obj.top - obj.getBoudingRect().top
还不够。我找不到一个案例,其中Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.top 将被视为obj.getBoundingRect().top is always negative
【参考方案1】:
我能够通过以下方式解决问题:
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>
【讨论】:
【参考方案2】:您可以设置对象修改侦听器并检查对象是否超出范围。如果是,则将其恢复到原始状态。
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();
);
【讨论】:
谢谢。这对我的使用进行了几分钟的更改。我感谢您的帮助。谢谢大佬, 需要注意的是,画布构建时要使用stateful = true 选项,否则object中不会有_stateProperties属性 我希望在运行时执行它而不是最后检查它如何实现?使用答案中提到的您的现有代码?【参考方案3】:如果你想进行实时预防,你应该使用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、top 和 scale 属性:
// 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 @kunalshaktawat,自从我发布答案以来,API 可能已经改变。检查文档以使用正确的方法和属性。 当对象旋转时不使用此代码,否则它工作正常。你能帮忙解决旋转对象缩放限制吗? @WilliamDias @AmanGojariya,旋转时,您必须为旋转事件创建一个处理程序。事件数据可能与缩放事件提供的数据不同。您必须根据需要调整代码。【参考方案4】:下面是从各个方向遮挡画布区域外任何物体坐标的代码
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();
);
【讨论】:
【参考方案5】:我能够使用最新版本的 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;
欢迎任何其他缩放对象的扩展。
【讨论】:
以上是关于防止 Fabric js 对象超出画布边界的主要内容,如果未能解决你的问题,请参考以下文章