将物体的边缘相互对齐,防止重叠
Snap edges of objects to each other and prevent overlap
我的目标是防止两个或多个矩形在我的FabricJS画布中重叠。
想象两个矩形都有位置和大小信息,你可以在画布中拖放任何矩形。
如果矩形A足够接近矩形B,则矩形A的位置应该对齐到矩形B的边缘。这应该适用于矩形B的任何边缘。顶点不必匹配,因为矩形的大小是可变的。
我有一个一维(x轴)的工作示例。
我最好的尝试
看到jsfiddle。
但是我需要它在两个维度上都绕矩形工作。我很确定,我的代码不能很好地管理这个
可能有帮助的代码片段:
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
该抓取应该适用于无限数量的对象(不仅仅是两个矩形)。
我自己解决了这个问题。参见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;
}
});
工作相当合法!干杯!
这是基于gco的回答,更新为与FabricJS 1.5.0一起工作,具有以下改进:
- 形状不重叠
- 快速响应。
- 图形包含在画布中。
JS Fiddle: 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);
}
}
});
});
我以@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;
}
});
});
我想知道的是是否有可能扩展它以增加以下功能:
- 动态拍摄
- 。在初始锁定后继续拖动对象将暂时禁用锁定,直到对象停止移动。例如,如果我将一个方框拖到另一个方框旁边,当它们在范围内时,它们就会粘合在一起。但是,如果我继续移动第一个框,我可以将它"放置"在一个位置,它在捕捉范围内,但不与另一个框对齐。
- 当选择的对象在另一个对象的范围内时显示指导线。目前,我们在目标对象周围添加了一个边界,但最好显示向外扩展的指导方针(可能到画布的边缘),以便更容易地可视化目标对象的边界。平行拍摄
- 。当移动一个已经被抓取到目标对象上的对象时,所选对象应该以这样的方式抓取到目标对象上,即两个对象的顶部、底部或侧面是平行的。例如,假设选定的正方形位于目标正方形的左侧,并且选定正方形的顶部低于目标正方形的顶部。将选定的方块向上移动将导致其顶部在目标范围内与目标顶部对齐。当向下移动时,或者选择的对象在目标上方/下方并且水平移动时,应该应用相同的逻辑。
我需要截取大小不等的区域。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);
相关文章:
- jquery移动对齐按钮取决于内容大小
- 如何在JavaScript图像滑块内居中对齐图像
- 为什么文本对齐:对;工作不正常
- 显示可链接的搜索结果+对齐方式
- 以Jquery为中心的Div中的图像对齐
- 无法将文本与图片垂直对齐
- 在这种情况下,如何正确对齐显示
- 使用jquery垂直对齐动态加载的图像
- 如何使 DIV 中的内容垂直对齐
- '填隙'当所述项目不在容器中时,将一些项目与引导程序网格对齐
- typeahead下拉结果向右对齐-向右拉
- css中的按钮对齐
- Wordpress 3级水平导航-需要jquery/javascript帮助进行对齐
- 嵌入的Vimeo与中心对齐
- HighMaps:在图例中对齐ColorAxis
- jqPlot对齐不同y轴的零
- 将两个图形(饼图和条形图)并排对齐::d3-js
- html根据DIRECTION设置更改元素的水平对齐
- 在圆上对齐 n 圆圈>没有重叠
- 将物体的边缘相互对齐,防止重叠