JSFiddle - React, Tailwind, and code Playground

by artoo

HTML

<div id="js-drop" class="drop-zone" data-order="2,4,5,3,6,1">
  <div class="droppable steps step-1"></div>
  <div class="droppable steps step-2"></div>
  <div class="droppable steps step-3"></div>
  <div class="droppable steps step-4"></div>
  <div class="droppable steps step-5"></div>
  <div class="droppable steps step-6"></div>
</div>

<div id="js-drag" class="drag-zone">
  <div class="drag-wrap">
    <img class="draggable" src="http://placehold.it/100x100">
  </div>
  <div class="drag-wrap">
    <img class="draggable" src="http://placehold.it/100x100">
  </div>
  <div class="drag-wrap">
    <img class="draggable" src="http://placehold.it/100x100">
  </div>
  <div class="drag-wrap">
    <img class="draggable" src="http://placehold.it/100x100">
  </div>
  <div class="drag-wrap">
    <img class="draggable" src="http://placehold.it/100x100">
  </div>
  <div class="drag-wrap">
    <img class="draggable" src="http://placehold.it/100x100">
  </div>
</div>

CSS

*, *:before, *:after {
  box-sizing: border-box;
}
html, body { height: 100%; }
body {
  margin: 0;
  background-color:#568eb9;
  background-image: linear-gradient(rgba(255,255,255,.3) 2px, transparent 2px),
  linear-gradient(90deg, rgba(255,255,255,.3) 2px, transparent 2px),
  linear-gradient(rgba(255,255,255,.2) 1px, transparent 1px),
  linear-gradient(90deg, rgba(255,255,255,.2) 1px, transparent 1px);
  background-size:100px 100px, 100px 100px, 20px 20px, 20px 20px;
  background-position:-2px -2px, -2px -2px, -1px -1px, -1px -1px
}
.drop-zone {
  height: 70vh;
  position: relative;
}
  .steps {
    position: absolute;
    height: 100px;
    width: 100px;
    background: white;
    border: 1px solid #568eb9;
    box-shadow: 0 5px 15px rgba(0,0,0,.2);
  }
    .step-1 { top:  50px; left:   50px; }
    .step-2 { top: 200px; left:   50px; }
    .step-3 { top: 250px; left:  200px; }
    .step-4 { top: 250px; left:  350px; }
    .step-5 { top: 250px; left:  500px; }
    .step-6 { top: 200px; left: 650px; }

    .can--drop:not(.caught--it):not(.can--catch) {
      /*animation: bounce 1.5s ease infinite;*/
      animation: glow 1.5s ease infinite;
    }

    @keyframes glow {
      50% {
        background: rgba(255,255,255,.6);
      }
    }

    @keyframes bounce {
      50% {
        transform:
          scale(.98)
          translateY(-5%);
      }
    }

    .steps:before {
      content: '';
      position: absolute;
      top: 0; left: 0;
      height: 100%; width: 100%;
      background: #6dc001;

      transform-origin: center;
      transform: scale(0);
    }
    .can--catch:before {
      animation: grow .75s ease infinite;
    }

    @keyframes grow {
      from { transform: scale(0); }
      to {
        opacity: 0;
        transform: scale(1);
      }
    }
    

.drag-zone {
  display: flex;
  align-items: center;
  justify-content: space-around;
  height: 30vh;
  background: rgba(255,255,255,.3);
  border-top: 1px solid rgba(255,255,255,.5);
}
 ...

JavaScript

(function() {

  function init() {
    var startPos = null;

    interact('.draggable').draggable({
      snap: {
        targets: [startPos],
        range: Infinity,
        relativePoints: [ { x: 0.5, y: 0.5 } ],
        endOnly: true
      },
      onstart: function (event) {
          var rect = interact.getElementRect(event.target);

          // record center point when starting the very first a drag
          startPos = {
            x: rect.left + rect.width  / 2,
            y: rect.top  + rect.height / 2
          }

        event.interactable.draggable({
          snap: {
            targets: [startPos]
          }
        });
      },
      // 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);
        target.classList.add('getting--dragged');
      },
      onend: function (event) {
        event.target.classList.remove('getting--dragged')
      }
    });

    interact('.droppable:not(.caught--it)').dropzone({
      accept: '.draggable',
      overlap: .5,

      ondropactivate: function (event) {
        event.target.classList.add('can--drop');
      },
      ondragenter: function (event) {
        var draggableElement = event.relatedTarget,
            dropzoneElement  = event.target,
            dropRect         = interact.getElementRect(dropzoneElement),
            dropCenter       = {
              x: dropRect.left + dropRect.width  / 2,
              y: dropRect.top  + dropRect.height /...