jQuery UI sortable tolerance option not working as expected

前端 未结 6 1588
[愿得一人]
[愿得一人] 2020-12-14 04:13

Here is the part from jQuery UI documentation for the tolerance option:

This is the way the reordering behaves during drag. Possible valu

相关标签:
6条回答
  • 2020-12-14 04:26

    For those with the same problem here's the workaround I use:

    $('#sortable').sortable({
        axis: 'x',
        sort: function (event, ui) {
            var that = $(this),
                w = ui.helper.outerWidth();
            that.children().each(function () {
                if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder')) 
                    return true;
                // If overlap is more than half of the dragged item
                var dist = Math.abs(ui.position.left - $(this).position().left),
                    before = ui.position.left > $(this).position().left;
                if ((w - dist) > (w / 2) && (dist < w)) {
                    if (before)
                        $('.ui-sortable-placeholder', that).insertBefore($(this));
                    else
                        $('.ui-sortable-placeholder', that).insertAfter($(this));
                    return false;
                }
            });
        },
    });​
    

    This works for a horizontal sortable, for a vertical one change outerWidth to outerHeight and position.left to position.top everywhere.

    Here's a complete working example

    0 讨论(0)
  • 2020-12-14 04:26

    Here is my implementation of the sort method for grid layout based on Pythagorean theorem and distance between compared elements' centers:

    $("#sortable").sortable({
        ...
        sort: sort_jQueryBug8342Fix
    });
    
    function sort_jQueryBug8342Fix(e, ui) {
    
        // ui-sortable-helper: element being dragged
        // ui-sortable-placeholder: invisible item on the original place of dragged item
        var container = $(this);
        var placeholder = container.children('.ui-sortable-placeholder');
    
        var draggedCenterX = ui.helper.position().left + ui.helper.outerWidth()/2;
        var draggedCenterY = ui.helper.position().top + ui.helper.outerHeight()/2;
    
        container.children().each(function () {
            var item = $(this);
    
            if(!item.hasClass( 'ui-sortable-helper' ) && !item.hasClass( 'ui-sortable-placeholder' ) ) {
                var itemCenterX = item.position().left + item.outerWidth()/2;
                var itemCenterY = item.position().top + item.outerHeight()/2;
    
                // Pythagorean theorem
                var distanceBetweenCenters = Math.sqrt(Math.pow(itemCenterX - draggedCenterX, 2) + Math.pow(itemCenterY - draggedCenterY, 2));
    
                var minDimension = Math.min(item.outerWidth(), item.outerHeight(), ui.helper.outerWidth(), ui.helper.outerHeight());
                var overlaps = distanceBetweenCenters < (minDimension / 2);
    
                if (overlaps) {
                    if (placeholder.index() > item.index()) {
                        placeholder.insertBefore(item);
                    } else {
                        placeholder.insertAfter(item);
                    }
                    container.sortable('refreshPositions');
                    return false;
                }
    
            }
        });
    }
    

    It works great for grid layouts and items with close width and height sizes, like image thumbnails of 4:3 or 16:10 ratio. In this case it needs 50-60% overlap to trigger sorting. If you have very wide elements, like 300x50 pixels (or elements of different size), then I suppose it will work too, but you would have to overlap them by X axis more, like 90%.

    Note: if you are sorting Dropzone.js elements, then && item.hasClass('dz-preview') should be added to first if statement.

    0 讨论(0)
  • 2020-12-14 04:29

    Expanding on dioslaska's answer slightly. Here's an example to do (what I'll call) 125% overlap http://jsfiddle.net/yj1wwtsd/4/.

    This only flips an element when you have gone past it by 25%. In my case I combine droppable and sortable and it feels better when the dragged item can completely overlap another (to allow dropping) before flipping.

    Of particular note is the jQueryUI hack on the last line. Without this it always defaults to intersect tolerance, even though the sort method is trying to do something different. This may break with future versions of jQueryUI (tested with 1.11.2).

    $('#sortable').sortable({
        axis: 'x',
        sort: function (event, ui) {
            var that = $(this);
            var w = ui.helper.outerWidth();
            var centreOfDraggedItem = ui.position.left + w/2;
    
            that.children().each(function () {
                if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder')) 
                    return true;
                var centreOfThisItem =  $(this).position().left + w/2;
                var dist = centreOfDraggedItem - centreOfThisItem;
                var before = centreOfDraggedItem > centreOfThisItem;
                if (Math.abs(dist) < w/2) {
                    if (before && dist > w/4) { // moving right
                        $('.ui-sortable-placeholder').insertAfter($(this));
                        return false;
                    }
                    if (!before && dist < -(w/4)) { // moving left
                        $('.ui-sortable-placeholder').insertBefore($(this));
                        return false;
                    }
                }
            });
        },
    });
    $('#sortable').data('ui-sortable')._intersectsWithPointer = function () {return false;};
    
    0 讨论(0)
  • 2020-12-14 04:31

    This happens exactly as you described in IE9, FF11 and Chrome18.

    Intersect overlaps when mouse is at least 50% above.
    I think this is the documentation who is not correct.

    EDIT: I found no bug related to this in JQuery Bugtracker.

    0 讨论(0)
  • 2020-12-14 04:43

    I have unevenly sized sortable items and this makes things event worse. I've modified dioslaska's code to change the order once the bottom of the handler passes the bottom of the child (when moving down) and the same for the tops ...

    sort: function (event, ui) {
        var that = $(this),
            draggedHeight = ui.helper.outerHeight(),
            originalTop = ui.originalPosition.top,
            draggedTop = ui.helper.position().top,
            draggedBottom = draggedTop + draggedHeight;
        that.children().each(function () {
            if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder')) {
                return true;
            }
            var childTop = $(this).position().top,
                childHeight = $(this).outerHeight(),
                childBottom = childTop + childHeight,
                isChildAfter = originalTop < childTop,
                largeTop,largeBottom,smallTop,smallBottom;
            if (childHeight > draggedHeight) {
                largeTop = childTop;
                largeBottom = childTop + childHeight;
                smallTop = draggedTop;
                smallBottom = draggedBottom;
            } else {
                smallTop = childTop;
                smallBottom = childTop + childHeight;
                largeTop = draggedTop;
                largeBottom = draggedBottom;
            }
            if (smallBottom > largeTop || smallTop < largeBottom) {
                if (isChildAfter && draggedBottom >= childBottom) {
                    $('.ui-sortable-placeholder', that).insertAfter($(this));
                } else if (!isChildAfter && draggedTop <= childTop) {
                    $('.ui-sortable-placeholder', that).insertBefore($(this));
                    return false;
                }
            }
        });
    }
    

    You can see the overlap detection is a little trickier than before as you may be dragging a large item over a small item or vice versa.

    It's not perfect, but it is a huge improvement over the default functionality.

    0 讨论(0)
  • 2020-12-14 04:44

    Here's an addition to @dioslaska's function. If you need to sort in a grid (e.g. your elements are floated), you can check the child element's top position to see if it's in the same "row" like so:

    sort: function (event, ui) {
      var self = $(this),
          width = ui.helper.outerWidth(),
          top = ui.helper.position().top;//changed to ;
    
      self.children().each(function () {
        if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder')) {
          return true;
        }
        // If overlap is more than half of the dragged item
        var distance = Math.abs(ui.position.left - $(this).position().left),
            before = ui.position.left > $(this).position().left;
    
        if ((width - distance) > (width / 2) && (distance < width) && $(this).position().top === top) {
          if (before) {
            $('.ui-sortable-placeholder', self).insertBefore($(this));
          } else {
            $('.ui-sortable-placeholder', self).insertAfter($(this));
          }
          return false;
        }
      });
    }
    
    0 讨论(0)
提交回复
热议问题