如何使用interact.js将元素捕捉到其他可拖动元素

时间:2022-11-22 13:44:26

I'm making a draggable elements using interactjs.io

我正在使用interactjs.io创建一个可拖动的元素

I need implement exactly the same behaviour that jQuery UI snap. You can see an example here in the official documentation:

我需要实现与jQuery UI snap相同的行为。您可以在官方文档中看到示例:

The behaviour is: snaps to all other draggable elements

行为是:捕捉到所有其他可拖动元素

In interactjs.io, in the documentation, you have "Snapping" (link documentation), but I don't find the way of coding it.

在interactjs.io中,在文档中,您有“Snapping”(链接文档),但我找不到编码方式。

I have created a fiddle here: Fiddle Link

我在这里创造了一个小提琴:小提琴链接

This is my JS Code:

这是我的JS代码:

interact('.draggable')
  .draggable({
   onmove: dragMoveListener,
   snap: {},
  });


  function dragMoveListener (event) {
    var target = event.target,
        // keep the dragged position in the data-x/data-y attributes
        x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
        y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

    // translate the element
    target.style.webkitTransform =
    target.style.transform =
      'translate(' + x + 'px, ' + y + 'px)';

    // update the position attributes
    target.setAttribute('data-x', x);
    target.setAttribute('data-y', y);
  }

I need modify the snap section code, to make the draggable items snapping with others.

我需要修改快照部分代码,以使可拖动项目与其他人对齐。

snap: {}

Thanks!!

2 个解决方案

#1


4  

The following code can give you some ideas to get the result that you want. It works with draggable elements of different sizes. Function targets are used to set the target points and lines.

以下代码可以为您提供一些想法,以获得您想要的结果。它适用于不同大小的可拖动元素。功能目标用于设置目标点和线。

You can test it in this jsfiddle.

你可以在这个jsfiddle中测试它。

var AXIS_RANGE = 12;
var CORNER_RANGE = 14;
var CORNER_EXCLUDE_AXIS = 8;
var AXIS_EXTRA_RANGE = -6;

var myItems = [];
var currentElement = null;
var offX1, offY1, offX2, offY2;

function getPosition(element) {
  return {
    x: parseFloat(element.getAttribute('data-x')) || 0,
    y: parseFloat(element.getAttribute('data-y')) || 0
  };
}

function isBetween(value, min, length) {
  return min - AXIS_EXTRA_RANGE < value && value < (min + length) + AXIS_EXTRA_RANGE;
}

function getDistance(value1, value2) {
  return Math.abs(value1 - value2);
}

function getSnapCoords(element, axis) {
  var result = {
    isOK: false
  };
  if (currentElement && currentElement !== element) {
    var pos = getPosition(element);
    var cur = getPosition(currentElement);
    var distX1a = getDistance(pos.x, cur.x);
    var distX1b = getDistance(pos.x, cur.x + currentElement.offsetWidth);
    var distX2a = getDistance(pos.x + element.offsetWidth, cur.x);
    var distX2b = getDistance(pos.x + element.offsetWidth, cur.x + currentElement.offsetWidth);
    var distY1a = getDistance(pos.y, cur.y);
    var distY1b = getDistance(pos.y, cur.y + currentElement.offsetHeight);
    var distY2a = getDistance(pos.y + element.offsetHeight, cur.y);
    var distY2b = getDistance(pos.y + element.offsetHeight, cur.y + currentElement.offsetHeight);
    var distXa = Math.min(distX1a, distX2a);
    var distXb = Math.min(distX1b, distX2b);
    var distYa = Math.min(distY1a, distY2a);
    var distYb = Math.min(distY1b, distY2b);
    if (distXa < distXb) {
      result.offX = offX1;
    } else {
      result.offX = offX2
    }
    if (distYa < distYb) {
      result.offY = offY1;
    } else {
      result.offY = offY2
    }
    var distX1 = Math.min(distX1a, distX1b);
    var distX2 = Math.min(distX2a, distX2b);
    var distY1 = Math.min(distY1a, distY1b);
    var distY2 = Math.min(distY2a, distY2b);
    var distX = Math.min(distX1, distX2);
    var distY = Math.min(distY1, distY2);
    var dist = Math.max(distX, distY);
    var acceptAxis = dist > CORNER_EXCLUDE_AXIS;

    result.x = distX1 < distX2 ? pos.x : pos.x + element.offsetWidth;
    result.y = distY1 < distY2 ? pos.y : pos.y + element.offsetHeight;

    var inRangeX1 = isBetween(pos.x, cur.x, currentElement.offsetWidth);
    var inRangeX2 = isBetween(cur.x, pos.x, element.offsetWidth);
    var inRangeY1 = isBetween(pos.y, cur.y, currentElement.offsetHeight);
    var inRangeY2 = isBetween(cur.y, pos.y, element.offsetHeight);

    switch (axis) {
      case "x":
        result.isOK = acceptAxis && (inRangeY1 || inRangeY2);
        break;
      case "y":
        result.isOK = acceptAxis && (inRangeX1 || inRangeX2);
        break;
      default:
        result.isOK = true;
        break;
    }
  }
  return result;
}

$('.draggable').each(function() {
  var pos = getPosition(this);
  this.style.transform = 'translate(' + pos.x + 'px, ' + pos.y + 'px)';
  myItems.push(getPosition(this));
});

interact('.draggable').draggable({
  onstart: function(event) {
    currentElement = event.target;
    var pos = getPosition(currentElement);
    offX1 = event.clientX - pos.x;
    offY1 = event.clientY - pos.y;
    offX2 = event.clientX - currentElement.offsetWidth - pos.x;
    offY2 = event.clientY - currentElement.offsetHeight - pos.y;
  },
  onmove: dragMoveListener,
  snap: {
    targets:
      (function() {
        var snapPoints = [];
        $('.draggable').each(function() {
          (function(element) {
            // Slide along the X axis
            snapPoints.push(
              function(x, y) {
                var data = getSnapCoords(element, "x");
                if (data.isOK) {
                  return {
                    x: data.x + data.offX,
                    range: AXIS_RANGE
                  };
                }
              });
            // Slide along the Y axis
            snapPoints.push(
              function(x, y) {
                var data = getSnapCoords(element, "y");
                if (data.isOK) {
                  return {
                    y: data.y + data.offY,
                    range: AXIS_RANGE
                  };
                }
              });
            // Snap to corner
            snapPoints.push(
              function(x, y) {
                var data = getSnapCoords(element);
                if (data.isOK) {
                  return {
                    x: data.x + data.offX,
                    y: data.y + data.offY,
                    range: CORNER_RANGE
                  };
                }
              });
          })(this);
        });
        return snapPoints;
      })()
  },
  onend: function(event) {
    $('.draggable').each(function() {
      currentElement = null;
      myItems.push(getPosition(this));
    });
  }
});

function dragMoveListener(event) {
  var target = event.target;
  var oldPos = getPosition(target);
  var x = oldPos.x + event.dx;
  var y = oldPos.y + event.dy;

  // keep the dragged position in the data-x/data-y attributes
  target.setAttribute('data-x', x);
  target.setAttribute('data-y', y);

  // translate the element
  target.style.webkitTransform =
    target.style.transform =
    'translate(' + x + 'px, ' + y + 'px)';

  $('#position').text('x: ' + x + ' - y: ' + y);

  var result = $.grep(myItems, function(e) {
    if (e.x == parseInt(target.getAttribute('data-x')) || e.y == parseInt(target.getAttribute('data-y')))
      return 1;
  });

  if (result.length >= 1)
    target.style.backgroundColor = '#CCC';
  else
    target.style.backgroundColor = '#FFF';
}

#2


1  

I made a JSFiddle without using interact.js. I only used jQuery. I did not use interactjs.io as you implied that you only prefer it but don't require it.

我没有使用interact.js就制作了一个JSFiddle。我只用过jQuery。我没有使用interactjs.io,因为你暗示你只是喜欢它但不需要它。

The code works with elements of different sizes.

该代码适用于不同大小的元素。

jQuery.fn.reverse = [].reverse;

/* Handle add button clicks*/
$(".add-draggable").click(function() {
  var newDraggable = jQuery("<div class='draggable'></div>");
  newDraggable.css({
    position: 'absolute',
    left: 150,
    top: 150
  })

  newDraggable.attr({
    'data-x': 150,
    'data-y': 150
  }).addClass("large");

  jQuery(".draggable-wapper").append(newDraggable)
});

// initiate blocks
// This is done in revers as when the element is absolutly positioned .poisition() will retrun differnt values for the next element (mostly x will be 0 for all elements)
$(".draggable").reverse().each(function(i, e) {
  _this = jQuery(this);
  position = _this.position();

  _this.css({
    position: 'absolute',
    left: position.left,
    top: position.top
  }).attr("data-y", position.top).attr("data-x", position.left);
});

// Set some variabkles

// Used to differentiate clicks on elements from dragging
var isDragging = false;

// Store coordiators of all elements 
var coord;

// The moving element
var element = null;

// The offset to which the moving element snaps to the target element
// in percentage
var snappingYOffset = 20;
var snappingXOffset = 20;


$(".draggable-wapper").on("mousedown", ".draggable", function() {

  _this = element = jQuery(this);
  coord = [];
  isDragging = true;

  // Update coord
  jQuery(".draggable").each(function(i, e) {
    if (i == element.index())
      return true;

    ele = jQuery(e);
    var position = ele.position();
    var elementData = getData(ele);

    coord[i] = {
      leftX: position.left,
      rightX: position.left + ele.outerWidth(),
      topY: position.top,
      bottomY: position.top + ele.outerHeight()
    }

    jQuery.extend(coord[i], elementData);
  });

  _this.removeData("last-position");
});

jQuery(document).on("mousemove", function(e) {
    if (!isDragging)
      return;


    var lastPosition = _this.data("last-position");
    element.addClass("moving");

    if (typeof lastPosition != 'undefined') {
      // get difference to detemine new position
      var xDelta = e.clientX - lastPosition.x;
      var yDelta = e.clientY - lastPosition.y;

      var elementX = parseInt(element.attr("data-x"));
      var elementY = parseInt(element.attr("data-y"));

      element.attr({
        "data-x": elementX + xDelta,
        "data-y": elementY + yDelta
      }).css({
        "left": elementX + xDelta,
        "top": elementY + yDelta
      });

      // find which element is closer to moving elements and within offset limits
      filterArray(coord, _this);
    }

    // Save values for next itertation
    var position = {
      x: e.clientX,
      y: e.clientY
    };

    element.data("last-position", position);
  })
  .on("mouseup", function() {
    if (isDragging) {
      isDragging = false;
      element.removeClass("moving");
    }
  });

function filterArray(array, element) {
  // Set coord for moving element 
  // x1: left, x2: right, y1: top, y2: bottom

  var elementX1 = parseInt(element.attr("data-x"));
  var elementX2 = elementX1 + element.outerWidth();

  var elementY1 = parseInt(element.attr("data-y"));
  var elementY2 = elementY1 + element.outerHeight();

  // Show value inside element
  element.html('y:' + elementY1 + '<br> x: ' + elementX1);

  var result = {};

  // Loop through other elements and match the closeset
  array.forEach(function(value, index, originalArray) {
    // Get coordinators of each element
    // x1: left, x2: right, y1: top, y2: bottom
    var x1 = value['leftX'];
    var x2 = value['rightX'];

    var y1 = value['topY'];
    var y2 = value['bottomY'];

    // Get which element is bigger; the moving or the target element
    var biggerDim = bigger(element, {
      height: value['height'],
      width: value['width']
    });

    // Show values inside element
    jQuery(".draggable").eq(index).html('y:' + y1 + '<br> x: ' + x1);

    // Get offset for partiuclar element
    var xOffset = value['xOffset'];
    var yOffset = value['yOffset'];

    // yRange checks if moving element is moving within the Y range of target element
    // This requried to snap if true
    var yRange = (biggerDim.height == 'moving') ? y1 >= (elementY1 - yOffset) && y2 <= (elementY2 + yOffset) : elementY1 > (y1 - yOffset) && elementY2 < (y2 + yOffset);

    // xRange checks if moving element is moving within the X range of target element
    // This requried to snap if true
    var xRange = (biggerDim.width == 'moving') ? x1 > (elementX1 - xOffset) && x2 < (elementX2 + xOffset) : elementX1 > (x1 - xOffset) && elementX2 < (x2 + xOffset);

    // Is source element (moving) within the Y range 
    if (yRange) {

      // Is source element within right range of target
      if (elementX1 >= (x2 - xOffset) && elementX1 <= (x2 + xOffset)) {
        // Left side of the moving element
        element.css({
          "left": x2
        });
        // Is source element within left range of target
      } else if (elementX2 >= (x1 - xOffset) && elementX2 <= (x1 + xOffset)) {
        // right side of the moving element
        element.css({
          "left": x1 - element.outerWidth()
        });
      }
    }

    // Is source element (moving) within the X range of target
    if (xRange) {
      if (elementY1 >= (y2 - yOffset) && elementY1 <= (y2 + yOffset)) {
        // Top side of the moving element
        element.css({
          "top": y2
        });
      } else if (elementY2 >= (y1 - yOffset) && elementY2 <= (y1 + yOffset)) {
        // bottom side of the moving element
        element.css({
          "top": y1 - element.outerHeight()
        });
      }
    }
  });
}

/* Find which element is bigger */
function bigger(moving, target) {
  var width1 = moving.outerWidth();
  var height1 = moving.outerHeight();

  var width2 = target.width;
  var height2 = target.height;

  var result = {
    width: 'target',
    height: 'target'
  };

  if (width1 > width2)
    result.width = 'moving';

  if (height1 > height2)
    result.height = 'moving';

  return result;
}

/* Get data releted to a certain element */
function getData(ele) {
  var height = ele.outerHeight();
  var width = ele.outerWidth();

  var xOffset = (width * snappingXOffset) / 100;
  var yOffset = (height * snappingYOffset) / 100;

  return {
    height: height,
    width: width,
    xOffset: xOffset,
    yOffset: yOffset
  }
}
.draggable {
  background-color: green;
  border: 1px solid white;
  box-sizing: border-box;
  cursor: move;
  float: left;
  padding: 5px;
  position: relative;
  color: white;
  font-family: "calibri", -webkit-touch-callout: none;
  /* iOS Safari */
  -webkit-user-select: none;
  /* Chrome/Safari/Opera */
  -khtml-user-select: none;
  /* Konqueror */
  -moz-user-select: none;
  /* Firefox */
  -ms-user-select: none;
  /* Internet Explorer/Edge */
  user-select: none;
  /* Non-prefixed version, currently
                                  not supported by any browser */
}
.draggable.large {
  height: 300px;
  width: 100px;
  font-size: 16px;
}
.draggable.small {
  height: 50px;
  width: 50px;
  font-size: 12px;
}
.draggable.medium {
  height: 100px;
  width: 80px;
  font-size: 12px;
}
.draggable-wapper {
  float: left;
  position: relative;
}
.moving {
  z-index: 2;
  background-color: purple;
}
.add-draggable {
  background-color: green;
  border: 1px solid #0a5e1d;
  border-radius: 5px;
  color: white;
  cursor: pointer;
  font-size: 19px;
  padding: 10px 20px;
  position: absolute;
  right: 15px;
  top: 15px;
  transition: all 0.5s ease 0s;
  font-family: "calibri",
}
.add-draggable:hover {
  opacity: 0.9;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='draggable-wapper'>
  <div class='draggable small'></div>
  <div class='draggable large'></div>
  <div class='draggable large'></div>
  <div class='draggable large'></div>
  <div class='draggable small'></div>
  <div class='draggable medium'></div>
  <div class='draggable medium'></div>
</div>

<div class='add-draggable'>
  Add
</div>

#1


4  

The following code can give you some ideas to get the result that you want. It works with draggable elements of different sizes. Function targets are used to set the target points and lines.

以下代码可以为您提供一些想法,以获得您想要的结果。它适用于不同大小的可拖动元素。功能目标用于设置目标点和线。

You can test it in this jsfiddle.

你可以在这个jsfiddle中测试它。

var AXIS_RANGE = 12;
var CORNER_RANGE = 14;
var CORNER_EXCLUDE_AXIS = 8;
var AXIS_EXTRA_RANGE = -6;

var myItems = [];
var currentElement = null;
var offX1, offY1, offX2, offY2;

function getPosition(element) {
  return {
    x: parseFloat(element.getAttribute('data-x')) || 0,
    y: parseFloat(element.getAttribute('data-y')) || 0
  };
}

function isBetween(value, min, length) {
  return min - AXIS_EXTRA_RANGE < value && value < (min + length) + AXIS_EXTRA_RANGE;
}

function getDistance(value1, value2) {
  return Math.abs(value1 - value2);
}

function getSnapCoords(element, axis) {
  var result = {
    isOK: false
  };
  if (currentElement && currentElement !== element) {
    var pos = getPosition(element);
    var cur = getPosition(currentElement);
    var distX1a = getDistance(pos.x, cur.x);
    var distX1b = getDistance(pos.x, cur.x + currentElement.offsetWidth);
    var distX2a = getDistance(pos.x + element.offsetWidth, cur.x);
    var distX2b = getDistance(pos.x + element.offsetWidth, cur.x + currentElement.offsetWidth);
    var distY1a = getDistance(pos.y, cur.y);
    var distY1b = getDistance(pos.y, cur.y + currentElement.offsetHeight);
    var distY2a = getDistance(pos.y + element.offsetHeight, cur.y);
    var distY2b = getDistance(pos.y + element.offsetHeight, cur.y + currentElement.offsetHeight);
    var distXa = Math.min(distX1a, distX2a);
    var distXb = Math.min(distX1b, distX2b);
    var distYa = Math.min(distY1a, distY2a);
    var distYb = Math.min(distY1b, distY2b);
    if (distXa < distXb) {
      result.offX = offX1;
    } else {
      result.offX = offX2
    }
    if (distYa < distYb) {
      result.offY = offY1;
    } else {
      result.offY = offY2
    }
    var distX1 = Math.min(distX1a, distX1b);
    var distX2 = Math.min(distX2a, distX2b);
    var distY1 = Math.min(distY1a, distY1b);
    var distY2 = Math.min(distY2a, distY2b);
    var distX = Math.min(distX1, distX2);
    var distY = Math.min(distY1, distY2);
    var dist = Math.max(distX, distY);
    var acceptAxis = dist > CORNER_EXCLUDE_AXIS;

    result.x = distX1 < distX2 ? pos.x : pos.x + element.offsetWidth;
    result.y = distY1 < distY2 ? pos.y : pos.y + element.offsetHeight;

    var inRangeX1 = isBetween(pos.x, cur.x, currentElement.offsetWidth);
    var inRangeX2 = isBetween(cur.x, pos.x, element.offsetWidth);
    var inRangeY1 = isBetween(pos.y, cur.y, currentElement.offsetHeight);
    var inRangeY2 = isBetween(cur.y, pos.y, element.offsetHeight);

    switch (axis) {
      case "x":
        result.isOK = acceptAxis && (inRangeY1 || inRangeY2);
        break;
      case "y":
        result.isOK = acceptAxis && (inRangeX1 || inRangeX2);
        break;
      default:
        result.isOK = true;
        break;
    }
  }
  return result;
}

$('.draggable').each(function() {
  var pos = getPosition(this);
  this.style.transform = 'translate(' + pos.x + 'px, ' + pos.y + 'px)';
  myItems.push(getPosition(this));
});

interact('.draggable').draggable({
  onstart: function(event) {
    currentElement = event.target;
    var pos = getPosition(currentElement);
    offX1 = event.clientX - pos.x;
    offY1 = event.clientY - pos.y;
    offX2 = event.clientX - currentElement.offsetWidth - pos.x;
    offY2 = event.clientY - currentElement.offsetHeight - pos.y;
  },
  onmove: dragMoveListener,
  snap: {
    targets:
      (function() {
        var snapPoints = [];
        $('.draggable').each(function() {
          (function(element) {
            // Slide along the X axis
            snapPoints.push(
              function(x, y) {
                var data = getSnapCoords(element, "x");
                if (data.isOK) {
                  return {
                    x: data.x + data.offX,
                    range: AXIS_RANGE
                  };
                }
              });
            // Slide along the Y axis
            snapPoints.push(
              function(x, y) {
                var data = getSnapCoords(element, "y");
                if (data.isOK) {
                  return {
                    y: data.y + data.offY,
                    range: AXIS_RANGE
                  };
                }
              });
            // Snap to corner
            snapPoints.push(
              function(x, y) {
                var data = getSnapCoords(element);
                if (data.isOK) {
                  return {
                    x: data.x + data.offX,
                    y: data.y + data.offY,
                    range: CORNER_RANGE
                  };
                }
              });
          })(this);
        });
        return snapPoints;
      })()
  },
  onend: function(event) {
    $('.draggable').each(function() {
      currentElement = null;
      myItems.push(getPosition(this));
    });
  }
});

function dragMoveListener(event) {
  var target = event.target;
  var oldPos = getPosition(target);
  var x = oldPos.x + event.dx;
  var y = oldPos.y + event.dy;

  // keep the dragged position in the data-x/data-y attributes
  target.setAttribute('data-x', x);
  target.setAttribute('data-y', y);

  // translate the element
  target.style.webkitTransform =
    target.style.transform =
    'translate(' + x + 'px, ' + y + 'px)';

  $('#position').text('x: ' + x + ' - y: ' + y);

  var result = $.grep(myItems, function(e) {
    if (e.x == parseInt(target.getAttribute('data-x')) || e.y == parseInt(target.getAttribute('data-y')))
      return 1;
  });

  if (result.length >= 1)
    target.style.backgroundColor = '#CCC';
  else
    target.style.backgroundColor = '#FFF';
}

#2


1  

I made a JSFiddle without using interact.js. I only used jQuery. I did not use interactjs.io as you implied that you only prefer it but don't require it.

我没有使用interact.js就制作了一个JSFiddle。我只用过jQuery。我没有使用interactjs.io,因为你暗示你只是喜欢它但不需要它。

The code works with elements of different sizes.

该代码适用于不同大小的元素。

jQuery.fn.reverse = [].reverse;

/* Handle add button clicks*/
$(".add-draggable").click(function() {
  var newDraggable = jQuery("<div class='draggable'></div>");
  newDraggable.css({
    position: 'absolute',
    left: 150,
    top: 150
  })

  newDraggable.attr({
    'data-x': 150,
    'data-y': 150
  }).addClass("large");

  jQuery(".draggable-wapper").append(newDraggable)
});

// initiate blocks
// This is done in revers as when the element is absolutly positioned .poisition() will retrun differnt values for the next element (mostly x will be 0 for all elements)
$(".draggable").reverse().each(function(i, e) {
  _this = jQuery(this);
  position = _this.position();

  _this.css({
    position: 'absolute',
    left: position.left,
    top: position.top
  }).attr("data-y", position.top).attr("data-x", position.left);
});

// Set some variabkles

// Used to differentiate clicks on elements from dragging
var isDragging = false;

// Store coordiators of all elements 
var coord;

// The moving element
var element = null;

// The offset to which the moving element snaps to the target element
// in percentage
var snappingYOffset = 20;
var snappingXOffset = 20;


$(".draggable-wapper").on("mousedown", ".draggable", function() {

  _this = element = jQuery(this);
  coord = [];
  isDragging = true;

  // Update coord
  jQuery(".draggable").each(function(i, e) {
    if (i == element.index())
      return true;

    ele = jQuery(e);
    var position = ele.position();
    var elementData = getData(ele);

    coord[i] = {
      leftX: position.left,
      rightX: position.left + ele.outerWidth(),
      topY: position.top,
      bottomY: position.top + ele.outerHeight()
    }

    jQuery.extend(coord[i], elementData);
  });

  _this.removeData("last-position");
});

jQuery(document).on("mousemove", function(e) {
    if (!isDragging)
      return;


    var lastPosition = _this.data("last-position");
    element.addClass("moving");

    if (typeof lastPosition != 'undefined') {
      // get difference to detemine new position
      var xDelta = e.clientX - lastPosition.x;
      var yDelta = e.clientY - lastPosition.y;

      var elementX = parseInt(element.attr("data-x"));
      var elementY = parseInt(element.attr("data-y"));

      element.attr({
        "data-x": elementX + xDelta,
        "data-y": elementY + yDelta
      }).css({
        "left": elementX + xDelta,
        "top": elementY + yDelta
      });

      // find which element is closer to moving elements and within offset limits
      filterArray(coord, _this);
    }

    // Save values for next itertation
    var position = {
      x: e.clientX,
      y: e.clientY
    };

    element.data("last-position", position);
  })
  .on("mouseup", function() {
    if (isDragging) {
      isDragging = false;
      element.removeClass("moving");
    }
  });

function filterArray(array, element) {
  // Set coord for moving element 
  // x1: left, x2: right, y1: top, y2: bottom

  var elementX1 = parseInt(element.attr("data-x"));
  var elementX2 = elementX1 + element.outerWidth();

  var elementY1 = parseInt(element.attr("data-y"));
  var elementY2 = elementY1 + element.outerHeight();

  // Show value inside element
  element.html('y:' + elementY1 + '<br> x: ' + elementX1);

  var result = {};

  // Loop through other elements and match the closeset
  array.forEach(function(value, index, originalArray) {
    // Get coordinators of each element
    // x1: left, x2: right, y1: top, y2: bottom
    var x1 = value['leftX'];
    var x2 = value['rightX'];

    var y1 = value['topY'];
    var y2 = value['bottomY'];

    // Get which element is bigger; the moving or the target element
    var biggerDim = bigger(element, {
      height: value['height'],
      width: value['width']
    });

    // Show values inside element
    jQuery(".draggable").eq(index).html('y:' + y1 + '<br> x: ' + x1);

    // Get offset for partiuclar element
    var xOffset = value['xOffset'];
    var yOffset = value['yOffset'];

    // yRange checks if moving element is moving within the Y range of target element
    // This requried to snap if true
    var yRange = (biggerDim.height == 'moving') ? y1 >= (elementY1 - yOffset) && y2 <= (elementY2 + yOffset) : elementY1 > (y1 - yOffset) && elementY2 < (y2 + yOffset);

    // xRange checks if moving element is moving within the X range of target element
    // This requried to snap if true
    var xRange = (biggerDim.width == 'moving') ? x1 > (elementX1 - xOffset) && x2 < (elementX2 + xOffset) : elementX1 > (x1 - xOffset) && elementX2 < (x2 + xOffset);

    // Is source element (moving) within the Y range 
    if (yRange) {

      // Is source element within right range of target
      if (elementX1 >= (x2 - xOffset) && elementX1 <= (x2 + xOffset)) {
        // Left side of the moving element
        element.css({
          "left": x2
        });
        // Is source element within left range of target
      } else if (elementX2 >= (x1 - xOffset) && elementX2 <= (x1 + xOffset)) {
        // right side of the moving element
        element.css({
          "left": x1 - element.outerWidth()
        });
      }
    }

    // Is source element (moving) within the X range of target
    if (xRange) {
      if (elementY1 >= (y2 - yOffset) && elementY1 <= (y2 + yOffset)) {
        // Top side of the moving element
        element.css({
          "top": y2
        });
      } else if (elementY2 >= (y1 - yOffset) && elementY2 <= (y1 + yOffset)) {
        // bottom side of the moving element
        element.css({
          "top": y1 - element.outerHeight()
        });
      }
    }
  });
}

/* Find which element is bigger */
function bigger(moving, target) {
  var width1 = moving.outerWidth();
  var height1 = moving.outerHeight();

  var width2 = target.width;
  var height2 = target.height;

  var result = {
    width: 'target',
    height: 'target'
  };

  if (width1 > width2)
    result.width = 'moving';

  if (height1 > height2)
    result.height = 'moving';

  return result;
}

/* Get data releted to a certain element */
function getData(ele) {
  var height = ele.outerHeight();
  var width = ele.outerWidth();

  var xOffset = (width * snappingXOffset) / 100;
  var yOffset = (height * snappingYOffset) / 100;

  return {
    height: height,
    width: width,
    xOffset: xOffset,
    yOffset: yOffset
  }
}
.draggable {
  background-color: green;
  border: 1px solid white;
  box-sizing: border-box;
  cursor: move;
  float: left;
  padding: 5px;
  position: relative;
  color: white;
  font-family: "calibri", -webkit-touch-callout: none;
  /* iOS Safari */
  -webkit-user-select: none;
  /* Chrome/Safari/Opera */
  -khtml-user-select: none;
  /* Konqueror */
  -moz-user-select: none;
  /* Firefox */
  -ms-user-select: none;
  /* Internet Explorer/Edge */
  user-select: none;
  /* Non-prefixed version, currently
                                  not supported by any browser */
}
.draggable.large {
  height: 300px;
  width: 100px;
  font-size: 16px;
}
.draggable.small {
  height: 50px;
  width: 50px;
  font-size: 12px;
}
.draggable.medium {
  height: 100px;
  width: 80px;
  font-size: 12px;
}
.draggable-wapper {
  float: left;
  position: relative;
}
.moving {
  z-index: 2;
  background-color: purple;
}
.add-draggable {
  background-color: green;
  border: 1px solid #0a5e1d;
  border-radius: 5px;
  color: white;
  cursor: pointer;
  font-size: 19px;
  padding: 10px 20px;
  position: absolute;
  right: 15px;
  top: 15px;
  transition: all 0.5s ease 0s;
  font-family: "calibri",
}
.add-draggable:hover {
  opacity: 0.9;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='draggable-wapper'>
  <div class='draggable small'></div>
  <div class='draggable large'></div>
  <div class='draggable large'></div>
  <div class='draggable large'></div>
  <div class='draggable small'></div>
  <div class='draggable medium'></div>
  <div class='draggable medium'></div>
</div>

<div class='add-draggable'>
  Add
</div>