【问题标题】:fabric.js arrows and arrow head rotatingfabric.js 箭头和箭头旋转
【发布时间】:2024-08-18 12:15:01
【问题描述】:

我想在 fabric.js 中创建将两个对象连接在一起的箭头。

我在这里有一个 jsFiddle 链接:http://jsfiddle.net/xvcyzh9p/45/(感谢@gco)。

上面允许您创建两个对象(两个矩形)并用一条线将它们连接在一起。

function addChildLine(options) {
canvas.off('object:selected', addChildLine);

// add the line
var fromObject = canvas.addChild.start;
var toObject = options.target;
var from = fromObject.getCenterPoint();
var to = toObject.getCenterPoint();
var line = new fabric.Line([from.x, from.y, to.x, to.y], {
    fill: 'red',
    stroke: 'red',
    strokeWidth: 5,
    selectable: false
});
canvas.add(line);
// so that the line is behind the connected shapes
line.sendToBack();

// add a reference to the line to each object
fromObject.addChild = {
    // this retains the existing arrays (if there were any)
    from: (fromObject.addChild && fromObject.addChild.from) || [],
    to: (fromObject.addChild && fromObject.addChild.to)
}
fromObject.addChild.from.push(line);
toObject.addChild = {
    from: (toObject.addChild && toObject.addChild.from),
    to: (toObject.addChild && toObject.addChild.to) || []
}
toObject.addChild.to.push(line);

// to remove line references when the line gets removed
line.addChildRemove = function () {
    fromObject.addChild.from.forEach(function (e, i, arr) {
        if (e === line)
            arr.splice(i, 1);
    });
    toObject.addChild.to.forEach(function (e, i, arr) {
        if (e === line)
            arr.splice(i, 1);
    });
}
canvas.addChild = undefined;
}

function addChildMoveLine(event) {
    canvas.on(event, function (options) {
        var object = options.target;
        var objectCenter = object.getCenterPoint();
        // udpate lines (if any)
        if (object.addChild) {
            if (object.addChild.from)
                object.addChild.from.forEach(function (line) {
                    line.set({ 'x1': objectCenter.x, 'y1': objectCenter.y });
                })
                if (object.addChild.to)
                    object.addChild.to.forEach(function (line) {
                        line.set({ 'x2': objectCenter.x, 'y2': objectCenter.y });
                    })
                    }

        canvas.renderAll();
    });
}

我曾尝试查看其他示例以在 fabric.js 中创建箭头,但在 gco 的小提琴中实现一直很痛苦。

我在这方面的最佳尝试可以在这里找到:http://example.legalobjects.com/

看起来像这样:

我遇到的一些问题是:

  • 箭头未按正确方向移动
  • 在画布上添加多个箭头时,箭头(或多个箭头)会断裂 - 由于某种原因,它们会“卡住”。
  • 箭头/线不在对象周围移动

添加多个箭头时会发生这种情况:

如果有人有任何想法或可以提供帮助,我将非常感激!

谢谢。

【问题讨论】:

标签: javascript canvas fabricjs


【解决方案1】:

让我们来看看你的问题。

在画布上添加多个箭头时,箭头(或多个箭头)会断裂 - 由于某种原因,它们会“卡住”。

这是由于您使用了单个triangle 全局变量造成的,这意味着实际上只能有一个三角形。不过,这很容易解决 - 只需将 triangle 替换为 line.triangle,这样它就会成为它所属行的属性。

例如而不是

triangle = new fabric.Triangle({

简单地使用

line.triangle = new fabric.Triangle({

然后替换

line.addChildRemove();
line.remove();
line.triangle.remove();

line.triangle.remove();
line.addChildRemove();
line.remove();

箭头没有朝正确的方向移动

这是您的方向逻辑的一个简单缺点。没有什么是真正错误的,只是它没有按照你的意愿去做。我将其改写为以下 sn-p:

object.addChild.to.forEach(function(line) {
    var x = objectCenter.x;
    var y = objectCenter.y;
    var xdis = REC_WIDTH/2 + TRI_WIDTH/2;
    var ydis = REC_HEIGHT/2 + TRI_HEIGHT/2;
    var horizontal = Math.abs(x - line.x2) > Math.abs(y - line.y2);
    line.set({
        'x2': x + xdis * (horizontal ? (x < line.x2 ? 1 : -1) :                      0),
        'y2': y + ydis * (horizontal ?                      0 : (y < line.y2 ? 1 : -1))
    });
    line.triangle.set({
        'left': line.x2, 'top': line.y2,
        'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
    });
});

基本思想是根据两个对象的X或Y距离是否更大,来偏移任何一个 X Y坐标。

箭头/线不在对象周围移动。

上面发布的 sn-p 中的另一个逻辑缺陷。使用x2y2 来计算三角形是正确的,因为您基于目标 矩形的位置。但是,对于该行,您希望基于 source 矩形的位置进行计算,因此您需要分别使用x1y1。所以我们可以再把上面的代码改成:

object.addChild.to.forEach(function(line) {
    var x = objectCenter.x;
    var y = objectCenter.y;
    var xdis = REC_WIDTH/2 + TRI_WIDTH/2;
    var ydis = REC_HEIGHT/2 + TRI_HEIGHT/2;
    var horizontal = Math.abs(x - line.x1) > Math.abs(y - line.y1);
    line.set({
        'x2': x + xdis * (horizontal ? (x < line.x1 ? 1 : -1) :                      0),
        'y2': y + ydis * (horizontal ?                      0 : (y < line.y1 ? 1 : -1))
    });
    line.triangle.set({
        'left': line.x2, 'top': line.y2,
        'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
    });
});

为了获得更无缝的体验,您还需要更改调用重新计算的方式。目前,您只更新连接到您实际移动的矩形的线的一部分,而不更新连接的另一半。
我通过将tofrom 数组替换为一个简单的lines 数组来实现这一点,并向每个行元素添加属性fromObjecttoObject - 在每次更新时,行的两端都会更新,就像这样:

if (object.addChild && object.addChild.lines) {
    object.addChild.lines.forEach(function(line) {
        var fcenter = line.fromObject.getCenterPoint(),
            fx = fcenter.x,
            fy = fcenter.y,
            tcenter = line.toObject.getCenterPoint(),
            tx = tcenter.x,
            ty = tcenter.y,
            xdis = REC_WIDTH/2 + TRI_WIDTH/2,
            ydis = REC_HEIGHT/2 + TRI_HEIGHT/2,
            horizontal = Math.abs(tx - line.x1) > Math.abs(ty - line.y1)
        line.set({
            'x1': fx,
            'y1': fy,
            'x2': tx + xdis * (horizontal ? (tx < line.x1 ? 1 : -1) :                       0),
            'y2': ty + ydis * (horizontal ?                       0 : (ty < line.y1 ? 1 : -1)),
        });
        line.triangle.set({
            'left': line.x2, 'top': line.y2,
            'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
        });
    });
}

除此之外,您还有一个未处理的用例:如果您选择一个框,将其删除,然后单击“添加子项”,则会出现错误。您可以通过简单地在addChild 函数顶部测试null 来防止这种情况发生,如下所示:

if(canvas.getActiveObject() == null)
{
    return;
}

通过以上所有内容,我创建了an updated fiddle


更新,几年后:事实证明,您可以拖动以选择多个矩形,这会将它们放在一个可以作为一个整体移动的组中,这样做会在我上面的解决方案中留下箭头(如cmets 中的“糖”)。解决方案是将更新代码嵌入到它自己的函数中,并递归调用该事件的目标对象的每个对象是一个组。然后您还会遇到对象坐标相对于组中心的问题,因此您也必须对此进行调整。我也修复了一个updated fiddle

新更新功能的代码为:

function addChildMoveLine(event) {
    canvas.on(event, function(options) {
        if((function updateObject(object, offset) {
            // update child objects, if any
            if (object.getObjects) {
                var off = object.getCenterPoint();
                return object.getObjects().reduce(function(flag, obj) {
                    return updateObject(obj, off) || flag;
                }, false);
            }
            if (!offset)
                offset = { x: 0, y: 0 };
            // otherwise udpate lines, if any
            if (object.addChild && object.addChild.lines) {
                object.addChild.lines.forEach(function(line) {
                    var fcenter = line.sourceObject.getCenterPoint(),
                        fx = fcenter.x + offset.x,
                        fy = fcenter.y + offset.y,
                        tcenter = line.targetObject.getCenterPoint(),
                        tx = tcenter.x + offset.x,
                        ty = tcenter.y + offset.y,
                        xdis = REC_WIDTH/2 + TRI_WIDTH/2,
                        ydis = REC_HEIGHT/2 + TRI_HEIGHT/2,
                        horizontal = Math.abs(tx - line.x1) > Math.abs(ty - line.y1);
                    line.set({
                        'x1': fx,
                        'y1': fy,
                        'x2': tx + xdis * (horizontal ? (tx < line.x1 ? 1 : -1) :                       0),
                        'y2': ty + ydis * (horizontal ?                       0 : (ty < line.y1 ? 1 : -1)),
                    });
                    line.triangle.set({
                        'left': line.x2, 'top': line.y2,
                        'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
                    });
                });
                return true; // re-render
            }
            return false; // no re-render needed
        })(options.target)) {
            canvas.renderAll();
        }
    });
}

【讨论】:

  • 不幸的是,当您在i.stack.imgur.com/ZDOUR.png 中看到的许多矩形和线条时,您的 JS Fiddle 仍然在handlind 线条中有一些错误。
  • 您能描述一下重现这种情况的步骤吗?我试过的一切都很好......
  • @Siguze,好吧,每次我有 3 个矩形和 2 行时,都会发生至少一个矩形的移动,不确定哪个,但很容易尝试所有 3 个。如果你有更多的矩形和线条,情况会变得更糟,请参阅i.stack.imgur.com/o3nLm.png
  • 没关系,我自己也发现了。 Fixed。还有什么?
  • 好吧,我不喜欢在代码中命名,实际上大部分都是,但我认为这不是你的问题。