Search code examples
javascriptjqueryjquery-uidraggabledroppable

How to get custom tolerance effect for .droppable()?


I've got a draggable #box div that I can drop onto one of multiple droppable .ru divs. The height of #box is about 3 times the height of a .ru div, however, so I'm playing around with the tolerance option of .droppable, but nothing seems to fit what I want.

I'd like for #box to be hovering over one of the droppables based on when the top third of #box is over one of the droppables.

I tried out using tolerance: pointer and setting the .draggable() option cursorAt: {top: 15}, but this makes #box jump to a new position when dragging is initiated, which I don't want.

I'm now trying adding a child div, #test 15px from the top of #box, and I'm wondering if there is a way for hovering to be activated on a .ru when #test is over that .ru? Or, alternatively, is there a good way to get hovering to activate as I've described that does not use a child div?

FIDDLE: https://jsfiddle.net/joL53wkq/4/

HTML:

<div id="containment">
    <div id="box">
        <div id="test">
        </div>
    </div>
</div>

CSS:

#box {
    background-color: teal;
    width: 500px;
    height: 92px;
    position: absolute;
    z-index: 50;
}  

#test{
    position: relative;
    top: 15px;
    width: 100%;
    height: 1px;
    background-color: blue;
}

#containment {
    background-color: #ddd;
    width: 500px;
    height: 800px;
}

.ru {
    background-color: red;
    width: 500px;
    height: 30px;
    margin-top: 1px;
}

.hover {
    background-color: yellow;
}

JS:

for(i=1; i<20; i++){
    $("#containment").append("<div class='ru'>")    
}

$( "#box" ).draggable({
    revert: "invalid",
});

$( ".ru" ).droppable({
    hoverClass: "hover",
    tolerance:"intersect",
    drop: function(event, ui) {
        ui.draggable.position({
            of: $(this),
            my: 'left top',
            at: 'left top'
        });
    }
});

Solution

  • The function managing the tolerance is called intersect, you could redefine it and add a custom tolerance option. Something like this seems to work, it may need some tweaking and testing, but it should give you some ideas:

    $.ui.intersect = function(draggable, droppable, toleranceMode) {
    
        if (!droppable.offset) {
            return false;
        }
    
        var draggableLeft, draggableTop,
            x1 = (draggable.positionAbs || draggable.position.absolute).left,
            y1 = (draggable.positionAbs || draggable.position.absolute).top,
            x2 = x1 + draggable.helperProportions.width,
            y2 = y1 + draggable.helperProportions.height,
            l = droppable.offset.left,
            t = droppable.offset.top,
            r = l + droppable.proportions.width,
            b = t + droppable.proportions.height;
    
        switch (toleranceMode) {
      case "custom":
      //you can define your rules here
                return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
                    x2 - (draggable.helperProportions.width / 2) < r && // Left Half
                    t < y1 && // Bottom Half
                    b > y1 + 15 ); // Top Half
            case "fit":
                return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
            case "intersect":
                return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
                    x2 - (draggable.helperProportions.width / 2) < r && // Left Half
                    t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
                    y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
            case "pointer":
                draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
                draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
                return isOverAxis( draggableTop, t, droppable.proportions().height ) && isOverAxis( draggableLeft, l, droppable.proportions().width );
            case "touch":
                return (
                    (y1 >= t && y1 <= b) || // Top edge touching
                    (y2 >= t && y2 <= b) || // Bottom edge touching
                    (y1 < t && y2 > b)      // Surrounded vertically
                ) && (
                    (x1 >= l && x1 <= r) || // Left edge touching
                    (x2 >= l && x2 <= r) || // Right edge touching
                    (x1 < l && x2 > r)      // Surrounded horizontally
                );
            default:
                return false;
            }
    
    };
    for(i=1; i<20; i++){
      $("#containment").append("<div class='ru'>")    
    }
    
    
    $( "#box" ).draggable({
      revert: "invalid",
    });
    
    
    $( ".ru" ).droppable({
      hoverClass: "hover",
      tolerance:"custom",
      drop: function(event, ui) {
        ui.draggable.position({
          of: $(this),
          my: 'left top',
          at: 'left top'
        });
      }
    });
    

    see https://jsfiddle.net/nxkfcwpp/1/