2

目标

  1. 根据位置将元素捕捉在一起或拒绝捕捉
  2. 根据捕捉位置更改元素外观

已经咨询过的消息来源

Jquery UI – 可拖动的“捕捉”事件

使用启用捕捉的可拖动 jQuery UI 检索“捕捉到”元素

jQuery UI 可拖动选项 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 个像素还不够——我正在考虑拖动元素的一半宽度或高度。数学很重要。

4

1 回答 1

1

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

正如评论中所讨论的,snapped如果捕捉的元素保持不变,则不会在拖动时再次触发事件,即使当辅助元素捕捉到的边缘发生变化时(这是我最初实现中的设计)。

为了克服这个限制,必须在处理程序中执行边缘检测,并且如果边缘发生变化,则必须触发drag其他事件。snapped

我们可以从计算一个元素与另一个元素的相对位置的效用函数开始。该函数返回人类可读的字符串,如"top left"or "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));
    }
}

这个解决方案在我的测试中给出了始终如一的准确结果。你会在这里找到一个更新的小提琴。


原答案如下:

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

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并且代码仅在snappingis时运行true。您可以在我根据您的示例创建的这个小提琴中对其进行测试。

也就是说,我认为您的问题可以简化很多。由于您只对水平边缘检测感兴趣,因此您只需将助手的左侧位置与捕捉元素的左侧位置进行比较。如果前者小于后者,则助手将向左对齐。否则它会向右对齐。

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

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("");
    }
}

你可以在这里测试它。

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

于 2013-03-24T07:19:30.093 回答