I'm making a draggable elements using interactjs.io
I need implement exactly the same behaviour that jQuery UI snap. You can see an example here in the official documentation:
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.
I have created a fiddle here: Fiddle Link
This is my JS Code:
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!!
I'm making a draggable elements using interactjs.io
I need implement exactly the same behaviour that jQuery UI snap. You can see an example here in the official documentation:
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.
I have created a fiddle here: Fiddle Link
This is my JS Code:
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!!
Share Improve this question edited Jan 15, 2016 at 14:09 chemitaxis asked Jan 15, 2016 at 12:50 chemitaxischemitaxis 14.9k17 gold badges77 silver badges128 bronze badges 3- Is using jQuery UI an option? – Kalimah Commented Jan 12, 2017 at 13:37
- I was hoping to avoid it because I liked interact.js's style, but it seems like it might be the best solution – cwj Commented Jan 13, 2017 at 8:22
- @KalimahApps nop in this moment, but thanks – chemitaxis Commented Jan 14, 2017 at 15:19
2 Answers
Reset to default 12 +150The 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.
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';
}
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.
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>