Search code examples
javascriptjqueryjquery-uidraggable

jQuery UI draggable: position in grid after drop


Using grid: [90, 83] when binding makes the element only move in the grid specified. I am attempting to make it move free (as it has no grid set) and then, when the user stops dragging the element, position it in a gird.

For example, on your Android smartphone, when you re-order the icons on the home screen, you can drag them wherever you like and after you release they stick to a grid.

So far I tried the following JS code:

$(".home-item.desktop-icon:last-child").draggable({
    //grid: [90, 83],
    containment: "parent",
    stop: function (event, ui) {
        y = ui.position.top,
        x = ui.position.left,
        _this = this;
        if ((x - 10)%83 >= 42) {
            posX(x-(x%83) + 93);
        }
        else {
            posX(x-(x%83) + 10);
        }
        if ((y - 10)%90 >= 45) {
            posY(y-(y%90) + 100);
        }
        else {
            posX(y-(y%90) + 10);
        }
        function posX (f) {
            $(_this).css("top", f + "px");
        }
        function posY (f) {
            $(_this).css("left", y + "px");
        }
    }
});

No idea why, however, this did not work at all. What should I do?


Solution

  • Here is an example based on my comment.

    $(function() {
      $(".draggable").draggable({
        containment: "parent"
      });
      $("#snaptarget").droppable({
        accept: ".draggable",
        drop: function(e, ui) {
          var cPPos = ui.position;
          var cOPos = ui.offset;
          var cPosOff = {
            top: Math.round(cOPos.top) % 90,
            left: Math.round(cOPos.left) % 83
          };
          var sPos = {
            top: Math.round(cOPos.top),
            left: Math.round(cOPos.left)
          };
          console.log("Dropped", cPPos, cOPos, cPosOff, sPos);
          var $item = ui.draggable;
          if (cPosOff.top > 0 && cPosOff.top < 70) {
            sPos.top = sPos.top - cPosOff.top;
            console.log("DROP - TOP: " + cOPos.top + " lower to " + sPos.top);
          } else {
            sPos.top = sPos.top + (90 - cPosOff.top);
            console.log("DROP - TOP: " + cOPos.top + " rise to " + sPos.top);
          }
          if (cPosOff.left > 0 && cPosOff.left < 61) {
            sPos.left = sPos.left - cPosOff.left;
            console.log("DROP - LEFT: " + cOPos.left + " left to " + sPos.left);
          } else {
            sPos.left = sPos.left + (83 - cPosOff.left);
            console.log("DROP - LEFT: " + cOPos.left + " right to " + sPos.left);
          }
          $item.appendTo($(this)).css({
            margin: 0,
            position: "absolute",
            top: sPos.top + "px",
            left: sPos.left + "px"
          });
        }
      });
    });
    .drag-area {
      min-height: 300px;
    }
    
    .draggable {
      width: 80px;
      height: 80px;
      display: inline-block;
      margin: 0 10px 10px 0;
      font-size: .9em;
    }
    
    .ui-widget-header p,
    .ui-widget-content p {
      margin: 0;
    }
    
    #snaptarget {
      height: 173px;
      position: relative;
    }
    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    
    <div class="drag-area">
      <div id="snaptarget" class="ui-widget-header">
      </div>
    
      <br style="clear:both">
    
      <div id="draggable" class="draggable ui-widget-content">
        <p>A</p>
      </div>
    
      <div id="draggable2" class="draggable ui-widget-content">
        <p>B</p>
      </div>
    
      <div id="draggable3" class="draggable ui-widget-content">
        <p>C</p>
      </div>
    
      <div id="draggable4" class="draggable ui-widget-content">
        <p>D</p>
      </div>
    
      <div id="draggable5" class="draggable ui-widget-content">
        <p>E</p>
      </div>
    </div>

    I left things a little un cleaned up so you get a little more feedback in Console. When dragging, you want a more full range of motion, but when the user drops the item, you want it to snap into a position.

    Initially, I was going for a 50% tolerance and found this was too greedy, so I shot for about a 75% tolerance. For example, if the user drops the item and it has a top,left of {12, 140} you may want it to snap to {0,83}. If the user is lazy or does not know it will snap, they may drag it close, with the intention of hitting the next target, my code will have a ~25% tolerance for the next block to the right. So if they drop at {12,164}, it will slide to the right block that is closer {0,166}.

    Hope that helps.