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

Version 1.2.2

Download the minified version
17.1KB gzipped
Download interact.min.js
Download the development version
36.6KB gzipped
Download interact.js

Dragging

<!-- enable javascript to view a demo -->
<div id="drag-me" class="draggable">
  <p> Click or touch and drag this element around </p>
</div>
/* enable javascript to view a demo */
#drag-me {
  width: 25%;
  height: 100%;
  margin: 10%;

  background-color: #29e;
  color: white;

  border: solid 0.4em #666;
  border-radius: 0.75em;
  padding: 3%;

  -webkit-transform: translate(0px, 0px);
          transform: translate(0px, 0px);
}

#drag-me::before {
  content: "#" attr(id);
  color: #000;
}
/* 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: function (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);
    },
    // 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');
    }
  });

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, border-color 0.3s;
}

.drop-active {
  border-color: #666;
}

.drop-target {
  background-color: #29e;
}

.drag-drop {
  display: inline-block;
  min-width: 40px;
  padding: 2em 0.5em;

  background-color: #99e;
  border: solid 2px white;

  -webkit-transform: translate(0px, 0px);
          transform: translate(0px, 0px);

  transition: background-color 0.3s;
}

.drag-drop.can-drop { 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: #4e4;
  color: #fff;
  font-size: 1.2em;
  border-radius: 4px;
  padding: 2%;
  margin: 5%;
}

#grid-snap::after {
  content: attr(data-x) ", " attr(data-y);
  background-color: #29e;
  color: #fff;
  float: right;
}
/* 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,
      elementOrigin: { 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"
       style="width: 100px; height: 200px">
     Resize from the bottom and right edges
  </div>
</div>
/* enable javascript to view a demo */
.resize {
  background-color: #29e;
  color: white;
  font-size: 20px;
  font-family: sans-serif;
  border-radius: 8px;
  box-sizing: border-box;
  padding: 20px;
  margin: 20px;

  /* The width is set in the element's HTML
   * so that it can be read by the resizing
   * function
   */
}

.resize-container {
  width: 100%;
  height: 240px;
}
/* enable javascript to view a demo */
interact('.resize')
  .resizable(true)
  .on('resizemove', function (event) {
    var target = event.target;

    // add the change in coords to the previous width of the target element
    var newWidth  = parseFloat(target.style.width ) + event.dx,
        newHeight = parseFloat(target.style.height) + event.dy;

    // update the element's style
    target.style.width  = newWidth + 'px';
    target.style.height = newHeight + 'px';

    target.textContent = newWidth + '×' + newHeight;
  });

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