简体   繁体   中英

JQuery Draggable - Prevent grid objects from going in the same position

I'm using JQuery Draggable to move items round a grid. Objects snap to a 32x32 grid area. I want to be able to cancel a grid snap if an object is in the same position.

The drag cannot be cancelled, it must just be prevented from entering the square. After it is prevented and moved back to the previous position, if the user continues to drag into a new unoccupied grid position, it must snap to that one.

I've created a demo which serves the purpose explained above however the image glitches when it tries to enter the new position but is then cancelled back to the old position.

https://jsfiddle.net/dtx7my4e/1/

Here's the code in that fiddle:

HTML:

   <div class="drop-target">
        <div class="drag-item" object-id="0"></div>
        <div class="drag-item" style="left: 32px;" object-id="1"></div>
    </div>

Javascript:

var objects = [
    [0, 0],
    [1, 1]
];

$(function() {
    $(".drag-item").draggable({
        grid: [ 32, 32 ],
        containment: '.drop-target',
        drag: function (event, obj){
            let objectId = $(this).attr('object-id');

            var objectPositionX = $(this).position().left / 32;
            var objectPositionY = $(this).position().top / 32;

            var previousPositionX = Math.floor(objects[objectId][0]);
            var previousPositionY = Math.floor(objects[objectId][1]);

            if (objectPositionX != previousPositionX || objectPositionY != previousPositionY) {
                if(!isObjectInPosition(objects, [objectPositionX, objectPositionY])) {
                    objects[objectId] = [objectPositionX, objectPositionY];
                } else {
                    obj.position.left = previousPositionX * 32;
                    obj.position.top = previousPositionY * 32;
                }
            }
        }
    });
});


function isObjectInPosition(arrayToSearch, positionToFind)
{
    for (let i = 0; i < arrayToSearch.length; i++) {
        if (arrayToSearch[i][0] == positionToFind[0]
                && arrayToSearch[i][1] == positionToFind[1]) {
            return true;
        }
    }
    return false;
}

CSS:

.drag-item {
    background-image: url("http://i.imgur.com/lBIWrWw.png");
    background-size: 32px auto;
    width: 32px;
    height: 32px;
    cursor: move;
}

.drop-target {
    background: whitesmoke url("http://i.imgur.com/uUvTRLx.png") repeat scroll 0 0 / 32px 32px;
    border: 1px dashed orange;
    height: 736px;
    left: 0;
    position: absolute;
    top: 0;
    width: 736px;
}

Thank you, any help is greatly appreciated.

Toby.

If you're willing to modify draggable itself, I think it would make the logic easier to apply. Once the drag event is triggered you can do lots of things, but you have much more control if you modify the _generatePosition method of draggable. It may look more complicated at first but for this kind of behavior, it's sometimes easier to work.

Basically, you can run your isInPosition function after the check for grid and containment has been applied. Normally next step is to set the new position, but if your isInPosition returns true, you prevent dragging. Something like this:

'use strict'
// This is the function generating the position by calculating
// mouse position, different offsets and option.

$.ui.draggable.prototype._generatePosition = function(event, constrainPosition) {
  var containment, co, top, left,
    o = this.options,
    scrollIsRootNode = this._isRootNode(this.scrollParent[0]),
    pageX = event.pageX,
    pageY = event.pageY;

  // Cache the scroll
  if (!scrollIsRootNode || !this.offset.scroll) {
    this.offset.scroll = {
      top: this.scrollParent.scrollTop(),
      left: this.scrollParent.scrollLeft()
    };
  }

  /*
   * - Position constraining -
   * Constrain the position to a mix of grid, containment.
   */

  // If we are not dragging yet, we won't check for options
  if (constrainPosition) {

    if (this.containment) {
      if (this.relativeContainer) {
        co = this.relativeContainer.offset();
        containment = [
          this.containment[0] + co.left,
          this.containment[1] + co.top,
          this.containment[2] + co.left,
          this.containment[3] + co.top
        ];
      } else {
        containment = this.containment;
      }

      if (event.pageX - this.offset.click.left < containment[0]) {
        pageX = containment[0] + this.offset.click.left;
      }
      if (event.pageY - this.offset.click.top < containment[1]) {
        pageY = containment[1] + this.offset.click.top;
      }
      if (event.pageX - this.offset.click.left > containment[2]) {
        pageX = containment[2] + this.offset.click.left;
      }
      if (event.pageY - this.offset.click.top > containment[3]) {
        pageY = containment[3] + this.offset.click.top;
      }
    }

    if (o.grid) {

      //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
      top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
      pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

      left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
      pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
    }

    if (o.axis === "y") {
      pageX = this.originalPageX;
    }

    if (o.axis === "x") {
      pageY = this.originalPageY;
    }
  }

// This is the only part added to the original function.
// You have access to the updated position after it's been
// updated through containment and grid, but before the
// element is modified.
// If there's an object in position, you prevent dragging.

  if (isObjectInPosition(objects, [pageX - this.offset.click.left - this.offset.parent.left, pageY - this.offset.click.top - this.offset.parent.top])) {
    return false;

  }

  return {
    top: (
      pageY - // The absolute mouse position
      this.offset.click.top - // Click offset (relative to the element)
      this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
      this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
      (this.cssPosition === "fixed" ? -this.offset.scroll.top : (scrollIsRootNode ? 0 : this.offset.scroll.top))
    ),
    left: (
      pageX - // The absolute mouse position
      this.offset.click.left - // Click offset (relative to the element)
      this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
      this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
      (this.cssPosition === "fixed" ? -this.offset.scroll.left : (scrollIsRootNode ? 0 : this.offset.scroll.left))
    )
  };

}

var objects = [
  [0, 0],
  [1, 1]
];

$(function() {
  $(".drag-item").draggable({
    grid: [32, 32],
    containment: '.drop-target',
    // on start you remove coordinate of dragged item
    // else it'll check its own coordinates
    start: function(event, obj) {
      var objectId = $(this).attr('object-id');
      objects[objectId] = [null, null];
    },
    // on stop you update your array
    stop: function(event, obj) {
      var objectId = $(this).attr('object-id');
      var objectPositionX = $(this).position().left / 32;
      var objectPositionY = $(this).position().top / 32;
      objects[objectId] = [objectPositionX, objectPositionY];

    }
  });
});


function isObjectInPosition(arrayToSearch, positionToFind) {

  for (let i = 0; i < arrayToSearch.length; i++) {
    if (arrayToSearch[i][0] === (positionToFind[0] / 32) && arrayToSearch[i][1] === (positionToFind[1] / 32)) {

      return true;
    }
  }
  return false;
}

https://jsfiddle.net/bfc4tsrh/1/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM