【问题标题】:Detecting Draggable snapper relative position to the element it snaps to检测可拖动捕捉器与其捕捉到的元素的相对位置
【发布时间】:2013-03-23 15:16:58
【问题描述】:

瞄准

  1. 根据位置将元素对齐或拒绝对齐
  2. 根据捕捉位置更改元素外观

已咨询的来源

Jquery UI – draggable 'snap' event

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

jQuery UI draggable options snaptolerance

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

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

示例代码 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. 如何拒绝捕捉/将位置标记为不可捕捉?

我需要将拖动的 div 中的图像更改为在它接近捕捉位置时指向正确的方向,所以如果你有一个可以显示这一点的数学建议,那就太好了。如您所见,3 个像素还不够——我正在考虑拖动元素的一半宽度或高度。数学很重要。

【问题讨论】:

    标签: jquery jquery-ui-draggable


    【解决方案1】:

    更新:鉴于下面的 cmets,这需要一个比我原来的答案更通用的解决方案。

    正如 cmets 中所讨论的,如果捕捉的元素保持不变,则不会在拖动时再次触发 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 事件:

    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

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

    【讨论】:

    • 非常感谢。我也对垂直捕捉感兴趣。只是有些物品可能只能水平拍摄,有些只能垂直拍摄 - 瓶子仅用于可视化 - 我更新了我的问题。顺便说一句,捕捉什么时候是假的?
    • 当我拖动你的 jsfiddle 时,我可以轻松地捕捉到顶部 as seen here 但文字仍然是角...。当我再次将它拖下来时,它说顶部 - 所以我真的需要知道如何使它更精确。也许我们也需要测量停止事件中的位置
    • @mplungjan,我明白你的意思了。这是因为当从top lefttop 再到top right 时,助手保持对齐到同一边缘,因此不会触发额外的snapped 事件。为了解决这个问题,必须在drag 处理程序中执行边缘检测,并且必须在边缘变化时触发额外的snapped 事件。如果/当我想出一个相当强大的解决方案时,我会试一试并更新我的答案。您是否知道用于区分的容差,例如top left 来自 top?它应该与“对齐”元素的大小有关吗?
    • 感谢收看。在我的现实生活中,snappers 和 snappees 不是很大,在 14 到 60 像素之间,我实际上需要将 14 像素的一个居中到 60 像素的一个(泵和肘关节)——最终我可以看到附加的东西肘关节实际上也需要与泵齐平......我不应该陷入这种混乱,但我们(你和我)最终可能会得到一些非常有用和可发布的东西
    • 此解决方案似乎仅在 snapMode 为外部时才有效,但如果两者都为外部则无效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-21
    • 1970-01-01
    • 2014-05-24
    • 1970-01-01
    • 1970-01-01
    • 2017-07-15
    • 2019-09-02
    相关资源
    最近更新 更多