JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+)
Version 1.3.3
<!-- enable javascript to view a demo -->
<div id="drag-1" class="draggable">
<p> You can drag one element </p>
</div>
<div id="drag-2" class="draggable">
<p> with each pointer </p>
</div>
/* enable javascript to view a demo */
#drag-1, #drag-2 {
width: 25%;
height: 100%;
min-height: 6.5em;
margin: 10%;
background-color: #29e;
color: white;
border-radius: 0.75em;
padding: 4%;
-webkit-transform: translate(0px, 0px);
transform: translate(0px, 0px);
}
#drag-me::before {
content: "#" attr(id);
font-weight: bold;
}
/* enable javascript to view a demo */
// target elements with the "draggable" class
interact('.draggable')
.draggable({
// enable inertial throwing
inertia: true,
// keep the element within the area of it's parent
restrict: {
restriction: "parent",
endOnly: true,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
},
// enable autoScroll
autoScroll: true,
// call this function on every dragmove event
onmove: dragMoveListener,
// call this function on every dragend event
onend: function (event) {
var textEl = event.target.querySelector('p');
textEl && (textEl.textContent =
'moved a distance of '
+ (Math.sqrt(Math.pow(event.pageX - event.x0, 2) +
Math.pow(event.pageY - event.y0, 2) | 0))
.toFixed(2) + 'px');
}
});
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 posiion attributes
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
// this is used later in the resizing and gesture demos
window.dragMoveListener = dragMoveListener;
<!-- enable javascript to view a demo -->
<div id="no-drop" class="draggable drag-drop"> #no-drop </div>
<div id="yes-drop" class="draggable drag-drop"> #yes-drop </div>
<div id="outer-dropzone" class="dropzone">
#outer-dropzone
<div id="inner-dropzone" class="dropzone">#inner-dropzone</div>
</div>
/* enable javascript to view a demo */
#outer-dropzone {
height: 140px;
}
#inner-dropzone {
height: 80px;
}
.dropzone {
background-color: #ccc;
border: dashed 4px transparent;
border-radius: 4px;
margin: 10px auto 30px;
padding: 10px;
width: 80%;
transition: background-color 0.3s;
}
.drop-active {
border-color: #aaa;
}
.drop-target {
background-color: #29e;
border-color: #fff;
border-style: solid;
}
.drag-drop {
display: inline-block;
min-width: 40px;
padding: 2em 0.5em;
color: #fff;
background-color: #29e;
border: solid 2px #fff;
-webkit-transform: translate(0px, 0px);
transform: translate(0px, 0px);
transition: background-color 0.3s;
}
.drag-drop.can-drop {
color: #000;
background-color: #4e4;
}
/* enable javascript to view a demo */
/* The dragging code for '.draggable' from the demo above
* applies to this demo as well so it doesn't have to be repeated. */
// enable draggables to be dropped into this
interact('.dropzone').dropzone({
// only accept elements matching this CSS selector
accept: '#yes-drop',
// Require a 75% element overlap for a drop to be possible
overlap: 0.75,
// listen for drop related events:
ondropactivate: function (event) {
// add active dropzone feedback
event.target.classList.add('drop-active');
},
ondragenter: function (event) {
var draggableElement = event.relatedTarget,
dropzoneElement = event.target;
// feedback the possibility of a drop
dropzoneElement.classList.add('drop-target');
draggableElement.classList.add('can-drop');
draggableElement.textContent = 'Dragged in';
},
ondragleave: function (event) {
// remove the drop feedback style
event.target.classList.remove('drop-target');
event.relatedTarget.classList.remove('can-drop');
event.relatedTarget.textContent = 'Dragged out';
},
ondrop: function (event) {
event.relatedTarget.textContent = 'Dropped';
},
ondropdeactivate: function (event) {
// remove active dropzone feedback
event.target.classList.remove('drop-active');
event.target.classList.remove('drop-target');
}
});
<!-- enable javascript to view a demo -->
<div id="grid-snap">
Dragging is constrained to the corners of a grid
</div>
/* enable javascript to view a demo */
#grid-snap {
width: 40%;
background-color: #29e;
color: #fff;
font-size: 1.2em;
border-radius: 4px;
padding: 2%;
margin: 5%;
}
<% end %>
/* enable javascript to view a demo */
var element = document.getElementById('grid-snap'),
x = 0, y = 0;
interact(element)
.draggable({
snap: {
targets: [
interact.createSnapGrid({ x: 30, y: 30 })
],
range: Infinity,
relativePoints: [ { x: 0, y: 0 } ]
},
inertia: true,
restrict: {
restriction: element.parentNode,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 },
endOnly: true
}
})
.on('dragmove', function (event) {
x += event.dx;
y += event.dy;
event.target.style.webkitTransform =
event.target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
});
<!-- enable javascript to view a demo -->
<div class="resize-container">
<div class="resize-drag">
Resize from any edge or corner
</div>
</div>
/* enable javascript to view a demo */
.resize-drag {
background-color: #29e;
color: white;
font-size: 20px;
font-family: sans-serif;
border-radius: 8px;
padding: 20px;
margin: 30px 20px;
width: 120px;
/* This makes things *much* easier */
box-sizing: border-box;
}
.resize-container {
display: inline-block;
width: 100%;
height: 240px;
}
/* enable javascript to view a demo */
interact('.resize-drag')
.draggable({
onmove: window.dragMoveListener,
restrict: {
restriction: 'parent',
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
},
})
.resizable({
// resize from all edges and corners
edges: { left: true, right: true, bottom: true, top: true },
// keep the edges inside the parent
restrictEdges: {
outer: 'parent',
endOnly: true,
},
// minimum size
restrictSize: {
min: { width: 100, height: 50 },
},
inertia: true,
})
.on('resizemove', function (event) {
var target = event.target,
x = (parseFloat(target.getAttribute('data-x')) || 0),
y = (parseFloat(target.getAttribute('data-y')) || 0);
// update the element's style
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.webkitTransform = target.style.transform =
'translate(' + x + 'px,' + y + 'px)';
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height);
});
<!-- enable javascript to view a demo -->
<div id="rotate-area">
<div id="angle-info">0°</div>
<svg id="arrow" viewbox="0 0 100 100">
<polygon points="50,0 75,25 62.5,25 62.5,100 37.5,100 37.5,25 25,25" fill="#29e"></polygon>
</svg>
</div>
/* enable javascript to view a demo */
#rotate-area {
overflow: hidden;
}
#arrow {
width: 100%;
height: 100%;
}
#angle-info {
color: #666;
font-size: 2em;
position: absolute;
}
/* enable javascript to view a demo */
var angle = 0;
interact('#rotate-area').gesturable({
onmove: function (event) {
var arrow = document.getElementById('arrow');
angle += event.da;
arrow.style.webkitTransform =
arrow.style.transform =
'rotate(' + angle + 'deg)';
document.getElementById('angle-info').textContent =
angle.toFixed(2) + '°';
}
});
<!-- enable javascript to view a demo -->
<!-- enable javascript to view a demo -->
<div id="gesture-area">
<img src="/images/ijs-256.png" id="scale-element">
</div>
/* enable javascript to view a demo */
#scale-element {
display: block;
max-width: 100%;
margin: auto;
}
#scale-element.reset {
transition: -webkit-transform 0.3s ease-in-out;
transition: transform 0.3s ease-in-out;
}
/* enable javascript to view a demo */
var scale = 1,
gestureArea = document.getElementById('gesture-area'),
scaleElement = document.getElementById('scale-element'),
resetTimeout;
interact(gestureArea)
.gesturable({
onstart: function (event) {
clearTimeout(resetTimeout);
scaleElement.classList.remove('reset');
},
onmove: function (event) {
scale = scale * (1 + event.ds);
scaleElement.style.webkitTransform =
scaleElement.style.transform =
'scale(' + scale + ')';
dragMoveListener(event);
},
onend: function (event) {
resetTimeout = setTimeout(reset, 1000);
scaleElement.classList.add('reset');
}
})
.draggable({ onmove: dragMoveListener });
function reset () {
scale = 1;
scaleElement.style.webkitTransform =
scaleElement.style.transform =
'scale(1)';
}
<!-- enable javascript to view a demo -->
<object id="star-demo" type="image/svg+xml" data="/images/star.svg"></object>
<p style="padding: 0 5%">
The <a href="/images/star.svg">star.svg</a> file references interact.js as
well as <a href="https://github.com/interactjs/website/blob/master/source/javascripts/star.js"> another script</a> to enable and
respond to drag events.
</p>
/* enable javascript to view a demo */
#star-demo {
display: block;
margin: auto;
}
<!-- enable javascript to view a demo -->
<div class="tap-target">
<p>Tap to change color</p>
<p>Doubletap to change size</p>
<p>Hold to rotate</p>
</div>
/* enable javascript to view a demo */
.tap-target {
width: 50%;
height: 100%;
padding: 10%;
margin: 10% auto;
border-radius: 100%;
font-size: 1.125em;
text-align: center;
color: #fff;
background-color: #29e;
cursor: pointer;
transition: all 0.3s;
}
.tap-target.switch-bg {
background-color: #f40;
}
.tap-target.large {
-webkit-transform: scale(1.25);
transform: scale(1.25);
}
.rotate {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
/* enable javascript to view a demo */
interact('.tap-target')
.on('tap', function (event) {
event.currentTarget.classList.toggle('switch-bg');
event.preventDefault();
})
.on('doubletap', function (event) {
event.currentTarget.classList.toggle('large');
event.currentTarget.classList.remove('rotate');
event.preventDefault();
})
.on('hold', function (event) {
event.currentTarget.classList.toggle('rotate');
event.currentTarget.classList.remove('large');
});