I was working on the same thing. This seems to be a pretty common feature request so I thought I'd share what I worked out. It could use some refinement. For instance, I've noticed if you scale the red box, the scale is not applied and it doesn't align right. However, I think it demonstrates the basic principal well and you can elaborate on it to fit your needs.
(Note: 8/1/2017: Working to place a more comprehensive code base on GitHub. (https://github.com/JerrodV/FabricObjectAlignment) More Details Soon!)
You can view the fiddle here
Html:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<canvas id="c" height="600" width="600" style="border:1px solid #c1c1c1;"></canvas>
</div>
</form>
<script src="Scripts/jquery-3.1.1.min.js"></script>
<script src="Scripts/fabric.min.js"></script>
<script src="Scripts/default.js"></script>
</body>
</html>
Javascript:
Def = {
canvas: null,
rect: null,
lines: {
top: null,
left: null,
right: null,
bottom: null
},
init: function () {
Def.canvas = new fabric.Canvas('c');
Def.canvas.on('object:moving', Def.events.objectMoving);
Def.canvas.add(new fabric.Rect({
height: 100,
width: 100,
top: 100,
left: 200,
fill: 'black',
selectable: false
}));
Def.canvas.add(new fabric.Rect({
height: 100,
width: 100,
top: 300,
left: 100,
fill: 'black',
selectable: false
}));
Def.rect = new fabric.Rect({
height: 100,
width: 100,
top: 200,
left: 250,
fill: 'red'
});
Def.canvas.add(Def.rect);
},
events: {
objectMoving: function (e) {
//Get the object being manipulated
var obj = e.target;
//Set up an object representing its current position
var curPos = {
top: parseInt(obj.get('top')),
left: parseInt(obj.get('left')),
right: parseInt(obj.get('left') + obj.get('width')),
bottom: parseInt(obj.get('top') + obj.get('height'))
};
//Set up an object that will let us be able to keep track of newly created lines
var matches = {
top: false,
left: false,
right: false,
bottom: false
}
//Get the objects from the canvas
var objects = Def.canvas.getObjects();
//For each object
for (var i in objects) {
//If the object we are looing at is a line or the object being manipulated, skip it
if (objects[i] === obj || objects[i].get('type') === 'line') { continue; }
//Set up an object representing the position of the canvas object
var objPos = {
top: parseInt(objects[i].get('top')),
left: parseInt(objects[i].get('left')),
right: parseInt(objects[i].get('left') + obj.get('width')),
bottom: parseInt(objects[i].get('top') + obj.get('height'))
}
//Look at all 4 sides of the object and see if the object being manipulated aligns with that side.
//Top////////////////////////////////////
if (Def.inRange(objPos.top, curPos.top)) {
//We match. If we don't already have aline on that side, add one.
if (!Def.lines.top) {
Def.drawLine('top', objPos.top);
//Keep track of the fact we found a match so we don't remove the line prematurely.
matches.top = true;
//Snap the object to the line
obj.set('top', objPos.top).setCoords();
}
}
//Left////////////////////////////////////
if (Def.inRange(objPos.left, curPos.left)) {
if (!Def.lines.left) {
Def.drawLine('left', objPos.left);
matches.left = true;
obj.set('left', objPos.left).setCoords();
}
}
//Right////////////////////////////////////
if (Def.inRange(objPos.right, curPos.right)) {
if (!Def.lines.right) {
Def.drawLine('right', objPos.right);
matches.right = true;
obj.set('left', objPos.right - objects[i].get('width')).setCoords();
}
}
//Bottom////////////////////////////////////
if (Def.inRange(objPos.bottom, curPos.bottom)) {
if (!Def.lines.bottom) {
Def.drawLine('bottom', objPos.bottom);
matches.bottom = true;
obj.set('top', objPos.bottom - objects[i].get('height')).setCoords();
}
}
//Look at the side we matched on. If we did not match, and we have a line, remove the line.
for (var j in matches) {
var m = matches[j];
var line = Def.lines[j];
if (!m && line) {
Def.canvas.remove(line);
Def.lines[j] = null;
}
}
}
Def.canvas.renderAll();
}
},
drawLine: function (side, pos) {
var ln = null
switch (side) {
case 'top':
ln = new fabric.Line([Def.canvas.get('width'), 0, 0, 0], {
left: 0,
top: pos,
stroke: 'rgb(178, 207, 255)'
});
Def.lines.top = ln;
break;
case 'left':
ln = new fabric.Line([0, Def.canvas.get('height'), 0, 0], {
left: pos,
top: 0,
stroke: 'rgb(178, 207, 255)'
});
Def.lines.left = ln;
break;
case 'right':
ln = new fabric.Line([0, Def.canvas.get('height'), 0, 0], {
left: pos,
top: 0,
stroke: 'rgb(178, 207, 255)'
});
Def.lines.right = ln;
break;
case 'bottom':
ln = new fabric.Line([Def.canvas.get('width'), 0, 0, 0], {
left: 0,
top: pos,
stroke: 'rgb(178, 207, 255)'
});
Def.lines.bottom = ln;
break;
}
Def.canvas.add(ln).renderAll();
},
alignTolerance : 6,
inRange: function (val1, val2) {
if ((Math.max(val1, val2) - Math.min(val1, val2)) <= Def.alignTolerance) { return true; }
else { return false; }
}
};
$(Def.init);
I hope you find this useful. Good Luck!