Как привязать элементы к другим элементам перетаскивания, используя interactive.js

Я делаю перетаскиваемые элементы, используя interactjs.io

Мне нужно реализовать точно такое же поведение, что и jQuery UI. Вы можете увидеть пример в официальной документации :

Поведение: привязывается ко всем другим элементам перетаскивания

В interactivejs.io в документации у вас есть "привязка" (l документация по чернилам), но я не нахожу способ его кодирования.

Я создал скрипку здесь: Ссылка на скрипку

Это мой код 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);
  }

Мне нужно изменить код секции привязки, чтобы перетаскивать элементы с другими.

snap: {}

Спасибо!!

Ответ 1

Следующий код может дать вам некоторые идеи, чтобы получить желаемый результат. Он работает с перетаскиваемыми элементами разных размеров. Целевые объекты используются для установки целевых точек и линий.

Вы можете проверить его в этот 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

В соответствии с тем, что я нашел, я не думаю, что это выполнимо из коробки. Существует несколько вопросов о github с комментариями создателя по строкам:

Я думаю, что привязка нескольких draggables должна работать нормально, но проблема в том, что привязка основана на позиции указателя, и это не учитывает положение или размеры элемента.

https://github.com/taye/interact.js/issues/59#issuecomment-50981271

Лучшим решением, которое я могу придумать, является создание сетки, которая соответствует соотношению ваших перетаскиваемых элементов.

CSS

* { box-sizing: border-box; }

.draggable {
    position: absolute;
    width: 90px;
    height: 90px;
    padding: 5px;
    float: left;
    margin: 0 10px 10px 0;
    font-size: .9em;
    overflow:hidden;
}

JS:

targets: [
    interact.createSnapGrid({ x: 30, y: 30 })
],

https://jsfiddle.net/taea58g2/

Ответ 3

Я сделал JSFiddle без использования interactive.js. Я использовал только jQuery. Я не использовал interactivejs.io, поскольку вы подразумевали, что вы предпочитаете его, но не требуете.

Код работает с элементами разных размеров.

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>