2013年8月7日水曜日

jQuery UI Draggable で margin を設定するとずれる

jQuery UI の Draggable API で、センタリングした draggable 要素が実際に表示されている座標とは違う場所にドロップ判定が発生してしまいます。
cursorAt オプションを使用することでセンタリングしたまま正しい判定が可能です。

実際にこの挙動が起きるサンプルプログラムを作成しました。
http://okanoworld.appspot.com/sample/jqueryui/dnd_position/index.html

ソースコードはこちら
https://github.com/yokano/sample/tree/master/jqueryui/dnd_position



droppable 要素 1 つと、draggable 要素 2 つがあります。
すべて margin を auto にしてセンタリングしています。

緑の draggable 要素を droppable 要素にドラッグしても反応がありません。
実際には droppable 要素の右側へ移動させることで反応します。
これはドラッグ中に表示される helper が margin を計算しないため、
実際に使われる drop 判定座標がずれてしまっているためです。

青の draggable 要素はセンタリングされているにも関わらず正しく判定されますね。
こちらは cursorAt を使って正しい判定ができるようにしています。

公式の trac に登録されたチケットをみてみると、
ドラッグ時に表示される helper は margin を考慮してくれないと書かれています。
#9325 Draggable: helpers do not account for margin on draggable element - jQuery UI

対処法として cursorAt が使えると書いてあります。
cursorAt は draggable 要素をドラッグした時に本来表示される位置からずらして表示するためのオプションです。

例えば
$('#drag').draggable({
    cursorAt: {
        left: 30,
        top: 25
    }
});
とすると、ドラッグ中に draggable 要素の helper が本来表示される位置から左に30px、上に25pxずれて表示されます。

青の draggable について JavaScript でこんな感じに書きます。
$('#revise').on('mousedown', function() {
    // ずれている距離を計算
    var difference = $(this).offset().left + $(this).width() / 2;

    // 同じ距離だけ右にずらして位置を合わせる
    $(this).draggable('option', 'cursorAt', {right: difference});

    // ドラッグ中のセンタリングを解除する
    $(this).draggable('option', 'start', function() {
        $(this).removeClass('center');
    });
    $(this).draggable('option', 'stop', function() {
        $(this).addClass('center');
    });
});

まず offset() と width() を使ってずれている距離を計算します。(difference)
実際の判定座標が左にずれているので、cursorAt で右に戻します。
この状態でドラッグするとドロップ判定箇所は正しい場所になるのですが、
見た目上、ドラッグ中の要素が右へずれてしまうので、センタリングを解除します。
センタリングを解除すると要素が左端に移動するので、
右にずらした距離とプラマイゼロになってうまいこと元の場所に表示されます。
これで、見た目上は何も変わっていないのに、ドロップ判定だけを右へずらすことができます。

1 件のコメント: