You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Fabric.js中的对齐参考线始终显示在所有对象之上

Fabric.js 对齐参考线置顶显示问题解决

你的问题核心是Fabric.js中选中的对象会在主画布的渲染流程中被优先绘制在普通对象之上,直接添加到主画布的参考线会被选中对象遮挡。解决这个问题最直接的方式是利用Fabric内置的overlayCanvas——它是一个独立于主画布的顶层画布,所有绘制在上面的内容都会显示在主画布所有对象(包括选中对象)的上方。

修改步骤:

  1. 清空覆盖层画布:每次绘制参考线前,先清空overlayCanvas的内容,避免残留旧的参考线。
  2. 直接在overlayCanvas绘制参考线:不再创建fabric.Line对象添加到主画布,改用原生Canvas API在overlayCanvas的上下文中绘制线条和标记点。

修改后的代码片段:

const drawGuides = (obj) => {
    const targets = canvas.getObjects().filter((o) => o.type !== "line" && o !== obj);
    const ctx = canvas.overlayCanvas.getContext('2d');
    // 清空覆盖层画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    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;

    // 定义绘制样式
    ctx.strokeStyle = 'blue';
    ctx.lineWidth = 1;
    const markSize = 5;

    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(pointArray.length <= 3 ) return;

        const sortedPointArray = pointArray.sort((a, b) => a - b);

        // 绘制参考线
        switch (side) {
            case "top":
            case "bottom":
            case "centerY":
                // 绘制水平线
                ctx.beginPath();
                ctx.moveTo(sortedPointArray[0], value);
                ctx.lineTo(sortedPointArray[sortedPointArray.length - 1], value);
                ctx.stroke();
                // 绘制交叉标记
                sortedPointArray.forEach(point => {
                    ctx.beginPath();
                    ctx.moveTo(point - markSize, value - markSize);
                    ctx.lineTo(point + markSize, value + markSize);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(point - markSize, value + markSize);
                    ctx.lineTo(point + markSize, value - markSize);
                    ctx.stroke();
                });
                break;
            case "left":
            case "right":
            case "centerX":
                // 绘制垂直线
                ctx.beginPath();
                ctx.moveTo(value, sortedPointArray[0]);
                ctx.lineTo(value, sortedPointArray[sortedPointArray.length - 1]);
                ctx.stroke();
                // 绘制交叉标记
                sortedPointArray.forEach(point => {
                    ctx.beginPath();
                    ctx.moveTo(value - markSize, point - markSize);
                    ctx.lineTo(value + markSize, point + markSize);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(value - markSize, point + markSize);
                    ctx.lineTo(value + markSize, point - markSize);
                    ctx.stroke();
                });
                break;
        }
    });
}

关键说明:

  • 移除了原来创建fabric.Line并添加到主画布的逻辑,改用overlayCanvas的上下文直接绘制,确保内容始终在顶层。
  • 每次绘制前清空overlayCanvas,避免残留之前的参考线。
  • 不需要再维护obj.guides和obj.guidePoints,因为参考线不再是Fabric对象,而是直接绘制在覆盖层上。

内容的提问来源于stack exchange,提问作者Youth Dream

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 15:07:02