reactjscanvasfabricjs

How to show guide lines at the top of the other objects include selected object in Fabirc.js?


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.

enter image description here

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.


Solution

  • 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.