JSFiddle - React, Tailwind, and code Playground

by Hugo Carneiro

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.touch/1.1.0/jquery.touch.min.js"></script>
<h1>Drag and Drop</h1>
<div id="wrapper">
  <div class="orderable vertical">
    <div class="item">A</div>
    <div class="item">
      <div class="orderable horizontal">
        <div class="item">B<sub>1</sub></div>
        <div class="item">B<sub>2</sub></div>
        <div class="item">B<sub>3</sub></div>
        <div class="item">B<sub>4</sub></div>
      </div>
    </div>
    <div class="item">C</div>
    <div class="item">
      <div class="orderable horizontal">
        <div class="item">D<sub>1</sub></div>
        <div class="item">D<sub>2</sub></div>
        <div class="item">D<sub>3</sub></div>
        <div class="item">D<sub>4</sub></div>
      </div>
    </div>
    <div class="item">E</div>
  </div>
</div>

CSS

#wrapper {
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;
}

.orderable {
  display: -moz-flex;
  display: -ms-flex;
  display: -webkit-flex;
  display: flex;
  font-size: 2rem;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
}

.orderable>.placeholder {
  background-color: rgba(0, 0, 0, 0.025);
  border-radius: 0.25rem;
  border: dashed 1px rgba(0, 0, 0, 0.25);
}

.orderable>.item {
  -moz-user-select: none;
  border-radius: 0.25rem;
  border: solid 1px rgba(0, 0, 0, 0.25);
  cursor: default;
  line-height: 1;
  padding: 1.25rem;
  text-align: center;
  transition: background-color 0.125s ease-in-out, transform 0.125s ease-in-out;
}

.orderable>.item.is-dragging {
  background-color: rgba(255, 255, 255, 0.875);
  box-shadow: 0 0.025em 0.25em 0 rgba(0, 0, 0, 0.125);
  cursor: move;
  margin: 0;
}

.orderable>.item.is-dropped {
  background-color: rgba(0, 224, 192, 0.325);
  transform: scale(1.05);
}

.orderable>.item.is-dropTarget {
  background-color: rgba(0, 224, 192, 0.075);
}

.orderable.horizontal {
  -moz-flex-direction: row;
  -ms-flex-direction: row;
  -webkit-flex-direction: row;
  flex-direction: row;
}

.orderable.horizontal>* {
  -moz-flex-grow: 1;
  -moz-flex-shrink: 1;
  -ms-flex-grow: 1;
  -ms-flex-shrink: 1;
  -webkit-flex-grow: 1;
  -webkit-flex-shrink: 1;
  flex-grow: 1;
  flex-shrink: 1;
  margin: 0 0 0 1.25rem;
  width: 100%;
}

.orderable.horizontal> :first-child {
  margin-left: 0;
}

.orderable.horizontal>.placeholder {
  -moz-flex-grow: 0 !important;
  -moz-flex-shrink: 0 !important;
  -ms-flex-grow: 0 !important;
  -ms-flex-shrink: 0 !important;
  -webkit-flex-grow: 0 !important;
  -webkit-flex-shrink: 0 !important;
  flex-grow: 0 !important;
  flex-shrink: 0 !important;
  width: 100%;
}

.orderable.horizontal>.item {
  width: 100%;
}

.orderable.horizontal>.item.is-dropTarget.a {
  border-bottom-left-radius: 0;
  border-left-color: #00E0C0;
  border-top-left-radius: 0;
  box-shadow: inset 3px 0 0 0...

JavaScript

$(function() {
  // Vars.
  var $document = $(document),
    $wrapper = $('#wrapper'),
    $placeholder = $('<div class="placeholder"></div>'),
    isDragging = false,
    hasDropTarget = false,
    isMobile = (navigator.userAgent.match(/(like Mac OS X|Android|Windows Phone)/) !== null);
  // Bind events.
  $wrapper
    .touch({
      // Turn on document tracking for smoother dragging.
      trackDocument: true,
      // Set drag threshold to zero for maximum drag sensitivity.
      dragThreshold: 0,
      // Set drag delay to zero for fastest drag response.
      dragDelay: 0,
      // Turn on drop filter (true = limit to siblings of item being dragged).
      dropFilter: true,
      // Delegate touch events to items.
      delegateSelector: '.item',
      // Lower tap and hold delay.
      tapAndHoldDelay: 250,
      // Prevent default events for drag events. Ordinarily this takes a boolean value, but in the case
      // of this demo we're doing the following to prevent:
      //
      // - If we're *not* on a mobile device, always prevent default drag events.
      // - If we *are* on a mobile device, only prevent default drag events when we're in a "tap and hold" gesture.
      preventDefault: {
        drag: (!isMobile ? true : function(t) {
          return t.inTapAndHold;
        })
      }
    })
    .on((!isMobile ? 'dragStart tapAndHold' : 'tapAndHold'), '.orderable > .item', function(event, o) {
      // Stop propagation.
      event.stopPropagation();
      // Vars.
      var $this = $(this),
        scrollLeft = $document.scrollLeft(),
        scrollTop = $document.scrollTop();
      // Already dragging? Bail.
      if (isDragging)
        return;
      // Set dragging state.
      isDragging = true;
      // Size placeholder.
      $placeholder
        .css('width', $this.outerWidth() + 'px')
        .css('height', $this.outerHeight() + 'px');
      // Size and position drag element and mark it as dragging.
      $this
        .css('width',...