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

目标

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

消息来源已经咨询过

Jquery UI – 可拖动的’snap’事件

使用jQuery UI检索“snapped to”元素,并且启用了snap

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) 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) 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个像素还不够 – 我正在考虑被拖动元素的一半宽度或高度。 数学很重要。

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

正如注释中所讨论的,如果捕捉的元素保持不变,则不会再次触发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)); } } 

该解决方案在我的测试中提供了一致的准确结 你会在这里找到一个更新的小提琴。


原始答案如下:

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

 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运行,则上面的代码给出了更好的结果。 你可以在我从你的例子创建的这个小提琴中测试它。

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

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

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

你可以在这里测试一下 。

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