I'm a beginner working with the Fabric.js canvas library and have implemented an object alignment feature that works well. However, I'm encountering an issue with the visual aspect of the alignment.
When I align two objects on the canvas, a guideline is drawn between them to indicate the alignment. The problem is that this guideline is not displayed on top of all objects; instead, it's hidden behind selected object, making it difficult to see.
I want to ensure that the guideline is always rendered on top of all objects, regardless of selection object.
I provide my current code.
const drawGuides = (obj) => {
const targets = canvas.getObjects().filter((o) => o.type !== "line" && o !== obj);
const objLeft = obj.left;
const objRight = obj.left + Math.round(obj.getScaledWidth());
const objCenterX = (objLeft + objRight) / 2.0;
const objTop = obj.top;
const objBottom = obj.top + Math.round(obj.getScaledHeight());
const objCenterY = (objTop + objBottom) / 2.0;
sides.forEach(side => {
let value;
let pointArray = [];
switch (side) {
case "top":
value = objTop;
pointArray = [objLeft, objRight, objCenterX];
break;
case "bottom":
value = objBottom;
pointArray = [objLeft, objRight, objCenterX];
break;
case "centerY":
value = objCenterY;
pointArray = [objLeft, objRight, objCenterX];
break;
case "left":
value = objLeft;
pointArray = [objTop, objBottom, objCenterY];
break;
case "right":
value = objRight;
pointArray = [objTop, objBottom, objCenterY];
break;
case "centerX":
value = objCenterX;
pointArray = [objTop, objBottom, objCenterY];
break;
}
for (const target of targets) {
const targetLeft = target.left;
const targetRight = target.left + Math.round(target.getScaledWidth());
const targetCenterX = (targetLeft + targetRight) / 2.0;
const targetTop = target.top;
const targetBottom = target.top + Math.round(target.getScaledHeight());
const targetCenterY = (targetTop + targetBottom) / 2.0;
switch (side) {
case "top":
case "bottom":
case "centerY":
if (inRange(value, targetTop) || inRange(value, targetBottom) || inRange(value, targetCenterY)) {
pointArray.push(targetLeft);
pointArray.push(targetRight);
pointArray.push(targetCenterX);
}
break;
case "left":
case "right":
case "centerX":
if (inRange(value, targetLeft) || inRange(value, targetRight) || inRange(value, targetCenterX)) {
pointArray.push(targetBottom);
pointArray.push(targetTop);
pointArray.push(targetCenterY);
}
break;
}
}
if (obj.guides[side] instanceof fabric.Line) {
// remove the line
canvas.remove(obj.guides[side]);
delete obj.guides[side];
}
if(obj.guidePoints[side] != null){
obj.guidePoints[side].forEach(mark=>{
canvas.remove(mark);
});
delete obj.guidePoints[side];
}
if(pointArray.length <= 3 ) return;
const sortedPointArray = pointArray.sort((a, b) => a - b);
let ln;
const lineProps = {
evented: true,
stroke: 'red',
selectable: false,
opacity: 1
};
let marks = [];
const markSize = 5;
switch (side) {
case "top":
case "bottom":
case "centerY":
ln = new fabric.Line(
[sortedPointArray[0], value, sortedPointArray[sortedPointArray.length - 1], value],
Object.assign(lineProps, {
stroke: 'blue'
})
);
sortedPointArray.forEach(point => {
marks.push(new fabric.Line([point - markSize, value - markSize, point + markSize, value + markSize],Object.assign(lineProps, { stroke: 'blue' })));
marks.push(new fabric.Line([point - markSize, value + markSize, point + markSize, value - markSize],Object.assign(lineProps, { stroke: 'blue' })));
});
break;
case "left":
case "right":
case "centerX":
ln = new fabric.Line(
[value, sortedPointArray[0], value, sortedPointArray[sortedPointArray.length - 1]],
Object.assign(lineProps, {
stroke: 'blue'
})
);
sortedPointArray.forEach(point => {
marks.push(new fabric.Line([value - markSize, point - markSize, value + markSize, point + markSize],Object.assign(lineProps, { stroke: 'blue' })));
marks.push(new fabric.Line([value - markSize, point + markSize, value + markSize, point - markSize],Object.assign(lineProps, { stroke: 'blue' })));
});
break;
}
obj.guides[side] = ln;
obj.guidePoints[side] = marks;
canvas.add(ln);
marks.forEach(mark => {canvas.add(mark);});
canvas.renderAll();
});
}
I tried to change z Index of guidelines but it's not solution.
As you can see in the image above, when you select a green rectangle object, its guide lines will appear below the object, but on top of other objects except itself. I always make sure all guidelines appear on top of all objects.
Oh, I found the solution. The reason is that the selected object is shown at the top of the all other objects in Fabric.js by default.
To solve the issue, I need to preserve order of the selected object by set preserveObjectStacking: true
.
In this case, all guide lines and marks are showed at the top of the selected object because they are added at the last.
It solved the my issue.