JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)

Version 1.2.4

Download the minified version
18.3KB gzipped
Download interact.min.js
Download the development version
39.5KB gzipped
Download interact.js

Dragging

<!-- 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 }
    },

    // 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(event.dx * event.dx +
                     event.dy * event.dy)|0) + '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 demo
  window.dragMoveListener = dragMoveListener;

Drag and drop

<!-- 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');
  }
});

Snapping

<!-- 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%;
}
/* 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)';
  });

Resizing

<!-- 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 {
  width: 100%;
  height: 240px;
}
/* enable javascript to view a demo */
interact('.resize-drag')
  .draggable({
    onmove: window.dragMoveListener
  })
  .resizable({
    edges: { left: true, right: true, bottom: true, top: 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 = event.rect.width + '×' + event.rect.height;
  });

Multi-touch Rotation

<!-- enable javascript to view a demo -->
<div id="rotate-area">
  <div id="angle-info">0&deg;</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" />
  </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) + '°';
  }
});

Pinch-to-zoom

<!-- enable javascript to view a demo -->
<div id="gesture-area">
  <img id="scale-element" src="/images/ijs-256.png" />
</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 + ')';
  },
  onend: function (event) {
    resetTimeout = setTimeout(reset, 1000);
    scaleElement.classList.add('reset');
  }
});

function reset () {
  scale = 1;
  scaleElement.style.webkitTransform =
  scaleElement.style.transform =
    'scale(1)';
}

// prevent browser's native drag on the image
gestureArea.addEventListener('dragstart', function (event) {
    event.preventDefault();
})

Use in SVG files

<!-- enable javascript to view a demo -->
<object id="star-demo" type="image/svg+xml" data="repo/demo/star.svg"></object>

<p>
  interact.js is referenced within the <a href="repo/demo/star.svg">star.svg</a>
  file as well as
  <a href="https://github.com/taye/interact.js/tree/master/demo/js/star.js">
  another script</a> to bind and handle drag event listeners
</p>

/* enable javascript to view a demo */
#star-demo {
  display: block;
  margin: auto;
}

Tap, doubletap and hold

<!-- 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');
  });