更新:以下のコメントに照らして、これには私の元の回答よりも一般的な解決策が必要です。
コメントで説明されているように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
いて、コードが の場合にのみ実行される場合、より良い結果をもたらしsnapping
ます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("");
}
}
ここでテストできます。
2 番目の質問については、ドラッグ可能なウィジェットが部分的な (水平のみの) スナップを (まだ) サポートしていないことを残念に思います。ただし、垂直スナップを無視して、水平位置に応じてヘルパーの画像を切り替え続けることもできます。ヘルパーが要素の上端と下端に近づいたときに発生する「ジャンプ」を除けば、コードは正常に動作するはずです。