更新:根据下面的评论,这需要一个比我原来的答案更通用的解决方案。
正如评论中所讨论的,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
并且代码仅在snapping
is时运行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("");
}
}
你可以在这里测试它。
关于您的第二个问题,恐怕可拖动小部件不支持部分(仅水平)捕捉(尚未)。但是,也许您可以忽略垂直对齐并根据其水平位置继续切换助手的图像。除了当助手接近元素的顶部和底部边缘时发生的“跳跃”之外,代码应该仍然可以正常工作。