将对象的边缘相互对齐并防止重叠
Posted
技术标签:
【中文标题】将对象的边缘相互对齐并防止重叠【英文标题】:Snap edges of objects to each other and prevent overlap 【发布时间】:2014-04-30 18:40:36 【问题描述】:我的目标是防止两个或多个矩形在我的 FabricJS 画布内重叠。
想象两个具有位置和大小信息的矩形,您可以在画布内拖放任何矩形。
如果矩形 A 与矩形 B 足够接近,矩形 A 的位置应该对齐矩形 B 的边缘。这应该适用于矩形 B 的任何边缘。顶点不必匹配,因为矩形是可变的。
我有一个在一维(x 轴)上捕捉的工作示例。
我最好的 jsfiddle 尝试
见jsfiddle。
但我需要它在两个维度上围绕矩形工作。我很确定,我的代码不足以管理这个。
可能有帮助的代码-sn-ps:
object.oCoords.tl.x //top-left corner x position. similar goes for top-right (tr), bottom-left (bl), bottom-right (br) and .y for y-position
mouse_pos = canvas.getPointer(e.e);
mouse_pos.x //pointer x.position
mouse_pos.y //pointer y.position
object.intersectsWithObject(targ) // object = dragged rectangle, targ = targeted rectangle
捕捉应该适用于无限数量的对象(不仅适用于两个矩形)。
【问题讨论】:
【参考方案1】:我自己解决了这个问题。 见 jsfiddle:http://jsfiddle.net/gcollect/FD53A/
这是代码:
this.canvas.on('object:moving', function (e)
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
canvas.forEachObject(function (targ)
var objects = this.canvas.getObjects(),
i = objects.length;
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection)
activeObject.left = targ.left - activeObject.currentWidth;
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection)
activeObject.left = targ.left + targ.currentWidth;
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection)
activeObject.top = targ.top - activeObject.currentHeight;
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection)
activeObject.top = targ.top + targ.currentHeight;
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject))
targ.strokeWidth = 10;
targ.stroke = 'red';
else
targ.strokeWidth = 0;
targ.stroke = false;
if (!activeObject.intersectsWithObject(targ))
activeObject.strokeWidth = 0;
activeObject.stroke = false;
);
工作非常合法!干杯!
【讨论】:
我真的很喜欢你在这里所做的。这里的一些小错误是,一旦对象可拖动,它往往会被锁定在网格上。此外,您仍然可以在对象中移动。试图想出与您所做的类似的事情,只是在关闭时才捕捉并防止重叠。 @ryan 非常感谢。有一阵子了。在我的用例中,我提出了解决方案,最好允许相交对象。因为否则用户必须绕过一个对象才能到达所需的目的地。也许它也适合您的用例。由于您可以检查对象何时相交,因此您可以提醒用户或禁用保存。 谁会在 2017 年阅读这篇文章,从 Fabric 1.5 开始,你应该使用.getWidth()
和 .getHeight()
而不是 .currentWidth
和 .currentHeight
。
也适用于 FabricJS 版本 4.3.1【参考方案2】:
这是基于 gco 的回答,更新后可与 FabricJS 1.5.0 一起使用,并进行了以下改进:
形状不重叠。 捕捉反应更快。 形状包含在画布中。JS 小提琴:https://jsfiddle.net/aphillips8/31qbr0vn/1/
var canvas = new fabric.Canvas('canvas'),
canvasWidth = document.getElementById('canvas').width,
canvasHeight = document.getElementById('canvas').height,
counter = 0,
rectLeft = 0,
snap = 20; //Pixels to snap
canvas.selection = false;
plusrect();
plusrect();
plusrect();
function plusrect(top, left, width, height, fill)
var rect = new fabric.Rect(
top: 300,
name: 'rectangle ' + counter,
left: 0 + rectLeft,
width: 100,
height: 100,
fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1,
maxWidth: canvasWidth,
maxHeight: canvasHeight
);
rect.custom = ;
rect.custom.counter = counter;
canvas.add(rect);
counter++;
rectLeft += 200;
function findNewPos(distX, distY, target, obj)
// See whether to focus on X or Y axis
if(Math.abs(distX) > Math.abs(distY))
if (distX > 0)
target.setLeft(obj.getLeft() - target.getWidth());
else
target.setLeft(obj.getLeft() + obj.getWidth());
else
if (distY > 0)
target.setTop(obj.getTop() - target.getHeight());
else
target.setTop(obj.getTop() + obj.getHeight());
canvas.on('object:moving', function (options)
// Sets corner position coordinates based on current angle, width and height
options.target.setCoords();
// Don't allow objects off the canvas
if(options.target.getLeft() < snap)
options.target.setLeft(0);
if(options.target.getTop() < snap)
options.target.setTop(0);
if((options.target.getWidth() + options.target.getLeft()) > (canvasWidth - snap))
options.target.setLeft(canvasWidth - options.target.getWidth());
if((options.target.getHeight() + options.target.getTop()) > (canvasHeight - snap))
options.target.setTop(canvasHeight - options.target.getHeight());
// Loop through objects
canvas.forEachObject(function (obj)
if (obj === options.target) return;
// If objects intersect
if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target))
var distX = ((obj.getLeft() + obj.getWidth()) / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
var distY = ((obj.getTop() + obj.getHeight()) / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);
// Set new position
findNewPos(distX, distY, options.target, obj);
// Snap objects to each other horizontally
// If bottom points are on same Y axis
if(Math.abs((options.target.getTop() + options.target.getHeight()) - (obj.getTop() + obj.getHeight())) < snap)
// Snap target BL to object BR
if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap)
options.target.setLeft(obj.getLeft() + obj.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
// Snap target BR to object BL
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap)
options.target.setLeft(obj.getLeft() - options.target.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
// If top points are on same Y axis
if(Math.abs(options.target.getTop() - obj.getTop()) < snap)
// Snap target TL to object TR
if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap)
options.target.setLeft(obj.getLeft() + obj.getWidth());
options.target.setTop(obj.getTop());
// Snap target TR to object TL
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap)
options.target.setLeft(obj.getLeft() - options.target.getWidth());
options.target.setTop(obj.getTop());
// Snap objects to each other vertically
// If right points are on same X axis
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - (obj.getLeft() + obj.getWidth())) < snap)
// Snap target TR to object BR
if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap)
options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight());
// Snap target BR to object TR
if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap)
options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
options.target.setTop(obj.getTop() - options.target.getHeight());
// If left points are on same X axis
if(Math.abs(options.target.getLeft() - obj.getLeft()) < snap)
// Snap target TL to object BL
if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap)
options.target.setLeft(obj.getLeft());
options.target.setTop(obj.getTop() + obj.getHeight());
// Snap target BL to object TL
if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap)
options.target.setLeft(obj.getLeft());
options.target.setTop(obj.getTop() - options.target.getHeight());
);
options.target.setCoords();
// If objects still overlap
var outerAreaLeft = null,
outerAreaTop = null,
outerAreaRight = null,
outerAreaBottom = null;
canvas.forEachObject(function (obj)
if (obj === options.target) return;
if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target))
var intersectLeft = null,
intersectTop = null,
intersectWidth = null,
intersectHeight = null,
intersectSize = null,
targetLeft = options.target.getLeft(),
targetRight = targetLeft + options.target.getWidth(),
targetTop = options.target.getTop(),
targetBottom = targetTop + options.target.getHeight(),
objectLeft = obj.getLeft(),
objectRight = objectLeft + obj.getWidth(),
objectTop = obj.getTop(),
objectBottom = objectTop + obj.getHeight();
// Find intersect information for X axis
if(targetLeft >= objectLeft && targetLeft <= objectRight)
intersectLeft = targetLeft;
intersectWidth = obj.getWidth() - (intersectLeft - objectLeft);
else if(objectLeft >= targetLeft && objectLeft <= targetRight)
intersectLeft = objectLeft;
intersectWidth = options.target.getWidth() - (intersectLeft - targetLeft);
// Find intersect information for Y axis
if(targetTop >= objectTop && targetTop <= objectBottom)
intersectTop = targetTop;
intersectHeight = obj.getHeight() - (intersectTop - objectTop);
else if(objectTop >= targetTop && objectTop <= targetBottom)
intersectTop = objectTop;
intersectHeight = options.target.getHeight() - (intersectTop - targetTop);
// Find intersect size (this will be 0 if objects are touching but not overlapping)
if(intersectWidth > 0 && intersectHeight > 0)
intersectSize = intersectWidth * intersectHeight;
// Set outer snapping area
if(obj.getLeft() < outerAreaLeft || outerAreaLeft == null)
outerAreaLeft = obj.getLeft();
if(obj.getTop() < outerAreaTop || outerAreaTop == null)
outerAreaTop = obj.getTop();
if((obj.getLeft() + obj.getWidth()) > outerAreaRight || outerAreaRight == null)
outerAreaRight = obj.getLeft() + obj.getWidth();
if((obj.getTop() + obj.getHeight()) > outerAreaBottom || outerAreaBottom == null)
outerAreaBottom = obj.getTop() + obj.getHeight();
// If objects are intersecting, reposition outside all shapes which touch
if(intersectSize)
var distX = (outerAreaRight / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
var distY = (outerAreaBottom / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);
// Set new position
findNewPos(distX, distY, options.target, obj);
);
);
【讨论】:
在某些情况下,形状可能会重叠。 imgur.com/a/42gWy 不完全确定为什么,但很好但可以重现。 1)调整中间形状。 2)将第一个形状放在右上角。 3) 逆时针将第三个形状拖到该对周围和上方。不过不错。【参考方案3】:我基于this fiddle @Anna Phillips' 和 @gco 的示例。它包括:
拐角捕捉 边缘捕捉 对象可以重叠 对象完全包含在画布中 对象的大小不能大于画布区域代码如下:
window.canvas = new fabric.Canvas('fabriccanvas');
window.counter = 0;
var newleft = 0,
edgedetection = 20, //pixels to snap
canvasWidth = document.getElementById('fabriccanvas').width,
canvasHeight = document.getElementById('fabriccanvas').height;
canvas.selection = false;
plusrect();
plusrect();
plusrect();
function plusrect(top, left, width, height, fill)
window.canvas.add(new fabric.Rect(
top: 300,
name: 'rectangle ' + window.counter,
left: 0 + newleft,
width: 100,
height: 100,
fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1,
maxHeight: document.getElementById("fabriccanvas").height,
maxWidth: document.getElementById("fabriccanvas").width,
));
window.counter++;
newleft += 200;
this.canvas.on('object:moving', function (e)
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
if(obj.getLeft() < edgedetection)
obj.setLeft(0);
if(obj.getTop() < edgedetection)
obj.setTop(0);
if((obj.getWidth() + obj.getLeft()) > (canvasWidth - edgedetection))
obj.setLeft(canvasWidth - obj.getWidth());
if((obj.getHeight() + obj.getTop()) > (canvasHeight - edgedetection))
obj.setTop(canvasHeight - obj.getHeight());
canvas.forEachObject(function (targ)
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection)
activeObject.left = targ.left - activeObject.currentWidth;
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection)
activeObject.left = targ.left + targ.currentWidth;
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection)
activeObject.top = targ.top - activeObject.currentHeight;
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection)
activeObject.top = targ.top + targ.currentHeight;
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject))
targ.strokeWidth = 10;
targ.stroke = 'red';
else
targ.strokeWidth = 0;
targ.stroke = false;
if (!activeObject.intersectsWithObject(targ))
activeObject.strokeWidth = 0;
activeObject.stroke = false;
);
);
我想知道的是是否可以扩展它以添加以下功能:
动态捕捉。在初始捕捉后继续拖动对象将暂时禁用捕捉,直到对象停止移动。例如,如果我将一个盒子拖到另一个盒子旁边,一旦它们在范围内,它们就会吸附在一起。但是,如果我继续移动第一个框,我可以将其“放置”在捕捉范围内但未与另一个框对齐的位置。 显示指南行当所选对象范围内的另一个对象范围内。目前我们在目标对象周围添加了一个边框,但最好显示向外延伸(可能到画布边缘)的准则,以便更轻松地可视化目标对象的边界。 平行捕捉。移动已捕捉到目标对象的对象时,所选对象应捕捉到目标对象,使两个对象的顶部、底部或侧面平行。例如,假设选定的正方形对齐到目标正方形的左侧,并且选定正方形的顶部低于目标正方形的顶部。向上移动所选方块应使其顶部在范围内与目标顶部对齐。向下移动时,或者如果所选对象高于/低于目标并水平移动,则应应用相同的逻辑。【讨论】:
【参考方案4】:我需要捕捉大小不等的区域。 jsfiddle
var canvas = new fabric.Canvas('c');
canvas.setDimensions(width:window.innerWidth);
var edge_detection_external = 21;
var corner_detection = 5;
canvas.selection = false;
canvas.on('object:moving', function (e)
var obj = e.target;
obj.setCoords();
function update_position(obj)
return function(targ)
if(targ === obj) return;
// Check overlap case https://www.geeksforgeeks.org/find-two-rectangles-overlap/
if(!(function(targ,obj)
if(obj.aCoords.tl.x > targ.aCoords.br.x || targ.aCoords.tl.x > obj.aCoords.br.x)
return false;
if(targ.aCoords.tl.y > obj.aCoords.br.y || obj.aCoords.tl.y > targ.aCoords.br.y)
return false;
return true;
)(targ,obj))
// is on RIGHT or LEFT?
if((obj.top > targ.top && obj.top < targ.top + targ.height)
|| (targ.top > obj.top && targ.top < obj.top + obj.height))
// Object is to the RIGHT and Edge detection
if(obj.aCoords.tl.x > targ.aCoords.br.x
&& obj.aCoords.tl.x - targ.aCoords.br.x < edge_detection_external)
obj.set(left:targ.aCoords.br.x);
// Corner detection
obj.setCoords();
if(Math.abs(targ.aCoords.tr.y - obj.aCoords.tl.y) < corner_detection)
obj.set(top:targ.top);
else if(Math.abs(targ.aCoords.br.y - obj.aCoords.bl.y) < corner_detection)
obj.set(top:targ.top + targ.height - obj.height);
// LEFT
if(targ.aCoords.tl.x > obj.aCoords.br.x
&& targ.aCoords.tl.x - obj.aCoords.br.x < edge_detection_external)
obj.set(left:targ.aCoords.tl.x - obj.width);
obj.setCoords();
if(Math.abs(targ.aCoords.tl.y - obj.aCoords.tr.y) < corner_detection)
obj.set(top:targ.top);
else if(Math.abs(targ.aCoords.bl.y - obj.aCoords.br.y) < corner_detection)
obj.set(top:targ.top + targ.height - obj.height);
// is on TOP or BOTTOM?
if((obj.left > targ.left && obj.left < targ.left + targ.width)
|| (targ.left > obj.left && targ.left < obj.left + obj.width))
// TOP
if(targ.aCoords.tl.y > obj.aCoords.br.y
&& targ.aCoords.tl.y - obj.aCoords.br.y < edge_detection_external)
obj.set(top:targ.aCoords.tl.y - obj.height);
obj.setCoords();
if(Math.abs(targ.aCoords.tl.x - obj.aCoords.bl.x) < corner_detection)
obj.set(left:targ.left);
else if(Math.abs(targ.aCoords.tr.x - obj.aCoords.br.x) < corner_detection)
obj.set(left:targ.left + targ.width - obj.width);
// BOTTOM
if(obj.aCoords.tl.y > targ.aCoords.br.y
&& obj.aCoords.tl.y - targ.aCoords.br.y < edge_detection_external)
obj.set(top:targ.aCoords.br.y);
obj.setCoords();
if(Math.abs(targ.aCoords.bl.x - obj.aCoords.tl.x) < corner_detection)
obj.set(left:targ.left);
else if(Math.abs(targ.aCoords.br.x - obj.aCoords.tr.x) < corner_detection)
obj.set(left:targ.left + targ.width - obj.width);
canvas.getObjects('group').some(update_position(obj));
);
String.prototype.to_inches = function()
return this.split('-').map(function(value,index)
value = Number(value);
if(index == 0)
return value * 12
else
return value
).reduce(function(total,current)
return total + current;
);
Array.prototype.to_object_list = function()
const preserved = [...this];
var header = this.splice(0,1)[0];
for(var i = 0;i < this.length; i++)
var obj = ;
for(var j = 0;j < header.length; j++)
obj[header[j].toLowerCase()] = this[i][j];
this[i] = obj;
return preserved;
function draw_areas()
var offset = 0;
return function(area_params,index)
if(area_params.area.indexOf('>') === -1)
var area = new fabric.Rect(
fill: 'red',
width:area_params.width.to_inches(),
height:area_params.length.to_inches(),
);
var text = new fabric.Text(area_params.area + '\n' + area_params.width + ' x ' + area_params.length,
fontSize:12,
fill:"white"
);
if(text.width - area.width > 0)
text.set('width',area.width);
if(text.height - area.height > 0)
text.set('height',area.height);
var group_name = 'group_' + area_params.area.split(' ').join('-');
var group = new fabric.Group([area,text],
name: group_name,
left: 5,
top: 5*(index++) + offset,
);
canvas.add(group);
offset = area_params.length.to_inches() + offset;
canvas.setDimensions(height:5*(index++) + offset);
function handler_get_data(data)
data = JSON.parse(data);
data.to_object_list();
data.forEach(draw_areas());
var d = '[["Area","Width","Length"],["Bedroom 1","19-5.5","14"],["Kitchen","14","16-3"],["Bedroom 2","13-6","12-9"]]';
handler_get_data(d);
【讨论】:
以上是关于将对象的边缘相互对齐并防止重叠的主要内容,如果未能解决你的问题,请参考以下文章