检测可拖动捕捉器相对于其捕捉到的元素的位置

时间:2013-03-23 15:16:59

标签: jquery jquery-ui-draggable

目标

  1. 将元素捕捉到一起或拒绝捕捉取决于位置
  2. 根据捕捉位置更改元素外观
  3. 已查询来源

    Jquery UI – draggable 'snap' event

    Retrieving the "snapped to" element using jQuery UI draggable with snap enabled

    jQuery UI draggable options snaptolerance

    可视化 想象一下带有连接器的div - 如果我将它连接到左侧瓶子,我需要连接器指向右侧,如果是正确的瓶子,连接器需要指向左侧。

    这种连接器不能卡在瓶子的顶部或底部,因此在这种情况下需要拒绝顶部或底部卡扣 - 其他情况可能只是垂直卡扣而其他情况可能会垂直或水平卡扣。最可能是水平或垂直

    enter image description here enter image description here

    示例代码 DEMO

    snapped: function(event, ui) {
        var snapper  = ui.snapElement.attr("id"),snapperPos = ui.snapElement.position(),
            snappee  = ui.helper.attr("id"),     snappeePos = ui.helper.position(),
            snapping = ui.snapping,
            collisionTolerance = 3;
    
        if (Math.abs(snapperPos.left-snappeePos.left)<=collisionTolerance) {
            if (snapperPos.top > snappeePos.top) ui.helper.html(showPos(snapper,"top",snapperPos,snappeePos));
            else ui.helper.html(showPos(snapper,"bottom",snapperPos,snappeePos));
        }
        else if (Math.abs(snapperPos.top-snappeePos.top)<=collisionTolerance) {
            if (snapperPos.left > snappeePos.left) ui.helper.html(showPos(snapper,"left",snapperPos,snappeePos));
            else ui.helper.html(showPos(snapper,"right",snapperPos,snappeePos));
        }
        else ui.helper.html(showPos(snapper,"corner",snapperPos,snappeePos));
    },
    

    问题

    1. 我如何可靠地检测到我确实已经快速捕捉到左侧,右侧,顶部或底部? - 看看我的意思,抓住偏见,然后弹出方框,但脚本是角落
    2. 如何拒绝快速/标记不可捕捉的位置?
    3. 我需要更改拖动div中的图像,使其在接近捕捉位置时指向正确的方向,因此如果您对数学建议可以显示这一点,那将会很棒。正如你所看到的,3个像素还不够 - 我正在考虑被拖动元素的一半宽度或高度。数学很重要。

1 个答案:

答案 0 :(得分:1)

  

更新:根据以下评论,这需要一个比原始答案更通用的解决方案。

正如评论中所讨论的,如果捕捉的元素保持不变,则snapped事件不会再次触发,即使辅助元素的边缘被捕捉更改(这是我原始实现中的设计) )。

要克服此限制,必须在drag处理程序中执行边缘检测,并且如果边缘发生更改,则必须触发其他snapped事件。

我们可以从一个效用函数开始,该函数计算元素与另一个元素的相对位置。该函数返回人类可读的字符串,如"top left""bottom right"。我选择使用元素宽度和高度的一半作为下面代码中角点的容差,如果它不适合你的可拖动元素的形状,当然可以修改它:

function computeRelativePosition($element, $reference)
{
    var result = [],
        vtol = $element.outerHeight() / 2,
        htol = $element.outerWidth() / 2,
        pos = $element.position(),
        bounds = $reference.position();
    $.extend(pos, {
        bottom: pos.top + $element.outerHeight() - 1,
        right: pos.left + $element.outerWidth() - 1
    });
    $.extend(bounds, {
        bottom: bounds.top + $reference.outerHeight() - 1,
        right: bounds.left + $reference.outerWidth() - 1
    });

    if (pos.top + vtol <= bounds.top) {
        result.push("top");
    } else if (pos.bottom - vtol >= bounds.bottom) {
        result.push("bottom");
    }

    if (pos.left + htol <= bounds.left) {
        result.push("left");
    } else if (pos.right - htol >= bounds.right) {
        result.push("right");
    }

    return result.join(" ");
}

从那里开始,我们必须在我们的drag处理程序中使用该函数,并且如果自上次处理程序运行以来助手对snapped元素的相对位置发生了变化,则会触发snapped事件:

drag: function(event, ui) {
    var draggable = $(this).data("draggable"); // "ui-draggable" with jQuery UI 1.10+
    $.each(draggable.snapElements, function(index, element) {
        ui = $.extend({}, ui, {
            snapElement: $(element.item),
            snapping: element.snapping
        });
        if (element.snapping) {
            var at = computeRelativePosition(ui.helper, ui.snapElement);
            if (!element.snappingKnown || at != element.at) {
                element.snappingKnown = true;
                element.at = ui.at = at;
                draggable._trigger("snapped", event, ui);
            }
        } else if (element.snappingKnown) {
            element.snappingKnown = false;
            draggable._trigger("snapped", event, ui);
        }
    });
} 

最后,我们可以将您的snapped处理程序更新为:

snapped: function(event, ui) {
    var snapper = ui.snapElement.attr("id"),
        snapperPos = ui.snapElement.position(),
        snappee = ui.helper.attr("id"),
        snappeePos = ui.helper.position(),
        snapping = ui.snapping;
    if (snapping) {
        ui.helper.html(showPos(snapper, ui.at, snapperPos, snappeePos));
    }
}

此解决方案在我的测试中提供始终如一的准确结果。你会发现一个更新的小提琴here


  

原始答案如下:

看起来你没有在计算中考虑辅助元素的宽度和高度。边缘检测代码应如下所示:

if (Math.abs(snapperPos.left - snappeePos.left) <= collisionTolerance) {
    if (snapperPos.top + ui.snapElement.outerHeight() > snappeePos.top) {
        ui.helper.html(showPos(snapper, "top", snapperPos, snappeePos));
    } else {
        ui.helper.html(showPos(snapper, "bottom", snapperPos, snappeePos));
    }
} else if (Math.abs(snapperPos.top - snappeePos.top) <= collisionTolerance) {
    if (snapperPos.left + ui.snapElement.outerWidth() > snappeePos.left) {
        ui.helper.html(showPos(snapper, "left", snapperPos, snappeePos));
    } else {
        ui.helper.html(showPos(snapper, "right", snapperPos, snappeePos));
    }
} else {
    ui.helper.html(showPos(snapper, "corner", snapperPos, snappeePos));
}

上面的代码为我提供了更好的结果,前提是collisionTolerance设置为50,代码仅在snappingtrue时运行。您可以在我根据您的示例创建的this fiddle中对其进行测试。

那就是说,我认为你的问题可以简化一点。由于您只对水平边缘检测感兴趣,因此只需将辅助对象的左侧位置与捕捉元素的左侧位置进行比较即可。如果前者小于后者,则帮助器会向左捕捉。否则它会向右移动。

我在对原始问题的回答中修改了小提琴以实现这一点(因为我比你的例子更熟悉它)。结果是:

snapped: function(event, ui) {
    ui.snapElement.toggleClass("ui-state-highlight ui-state-default");
    var status = ui.snapElement.find(".status");
    if (ui.snapping) {
        if (ui.helper.position().left < ui.snapElement.position().left) {
            status.text("left");
        } else {
            status.text("right");
        }
    } else {
        status.text("");
    }
}

您可以对其进行测试here

关于你的第二个问题,我担心可拖动的小部件不支持部分(仅水平)捕捉(尚未)。但是,也许您可​​以忽略垂直对齐并根据其水平位置继续切换辅助图像。除了帮助器靠近元素的顶部和底部边缘时发生的“跳转”,代码仍然可以正常工作。