<div class="drag-container"></div>
<div class="board">
  <div class="board-column todo">
    <div class="board-column-container">
      <div class="board-column-header">Todo</div>
      <div class="board-column-content-wrapper">
        <div class="board-column-content">
          <div class="board-item"><div class="board-item-content"><span>Item #</span>1</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>2</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>3</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>4</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>5</div></div>
        </div>
      </div>
    </div>
  </div>
  <div class="board-column working">
    <div class="board-column-container">
      <div class="board-column-header">Working</div>
      <div class="board-column-content-wrapper">
        <div class="board-column-content">
          <div class="board-item"><div class="board-item-content"><span>Item #</span>6</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>7</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>8</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>9</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>10</div></div>
        </div>
      </div>
    </div>
  </div>
  <div class="board-column done">
    <div class="board-column-container">
      <div class="board-column-header">Done</div>
      <div class="board-column-content-wrapper">
        <div class="board-column-content">
          <div class="board-item"><div class="board-item-content"><span>Item #</span>11</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>12</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>13</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>14</div></div>
          <div class="board-item"><div class="board-item-content"><span>Item #</span>15</div></div>
        </div>
      </div>
    </div>
  </div>
</div>
* {
  box-sizing: border-box;
}
html, body {
  position: relative;
  width: 100%;
  height: 100%;
  font-family: Helvetica, Arial, sans-serif;
}
body {
  margin: 0;
  padding: 20px 10px;
}
.drag-container {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 1000;
}
.board {
  position: relative;
}
.board-column {
  position: absolute;
  left: 0;
  top: 0;
  padding: 0 10px;
  width: calc(100% / 3);
  z-index: 1;
}
.board-column.muuri-item-releasing {
  z-index: 2;
}
.board-column.muuri-item-dragging {
  z-index: 3;
  cursor: move;
}
.board-column-container {
  position: relative;
  width: 100%;
  height: 100%;
}
.board-column-header {
  position: relative;
  height: 50px;
  line-height: 50px;
  overflow: hidden;
  padding: 0 20px;
  text-align: center;
  background: #333;
  color: #fff;
  border-radius: 5px 5px 0 0;
  font-weight: bold;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
@media (max-width: 600px) {
  .board-column-header {
    text-indent: -1000px;
  }
}
.board-column.todo .board-column-header {
  background: #4A9FF9;
}
.board-column.working .board-column-header {
  background: #f9944a;
}
.board-column.done .board-column-header {
  background: #2ac06d;
}
.board-column-content-wrapper {
  position: relative;
  padding: 8px;
  background: #f0f0f0;
  height: calc(100vh - 90px);
  overflow-y: auto;
  border-radius: 0 0 5px 5px;
}
.board-column-content {
  position: relative;
  min-height: 100%;
}
.board-item {
  position: absolute;
  width: calc(100% - 16px);
  margin: 8px;
}
.board-item.muuri-item-releasing {
  z-index: 9998;
}
.board-item.muuri-item-dragging {
  z-index: 9999;
  cursor: move;
}
.board-item.muuri-item-hidden {
  z-index: 0;
}
.board-item-content {
  position: relative;
  padding: 20px;
  background: #fff;
  border-radius: 4px;
  font-size: 17px;
  cursor: pointer;
  -webkit-box-shadow: 0px 1px 3px 0 rgba(0,0,0,0.2);
  box-shadow: 0px 1px 3px 0 rgba(0,0,0,0.2);
}
@media (max-width: 600px) {
  .board-item-content {
    text-align: center;
  }
  .board-item-content span {
    display: none;
  }
}
const dragStartData = new Map();
const dragContainer = document.querySelector('.drag-container');
const itemContainers = [].slice.call(document.querySelectorAll('.board-column-content'));
const columnGrids = [];
let boardGrid;

// Init the column grids so we can drag those items around.
itemContainers.forEach(function (container) {
  const grid = new Muuri(container, {
    items: '.board-item',
    dragEnabled: true,
    dragSort: function () {
      return columnGrids;
    },
    dragContainer: dragContainer,
    dragCssProps: {
      touchAction: 'auto',
      userSelect: 'none',
      userDrag: 'none',
      tapHighlightColor: 'rgba(0, 0, 0, 0)',
      touchCallout: 'none',
      contentZooming: 'none'
    },
    dragStartPredicate: function (item, e) {
      // On touch, let's allow dragging if the delay between
      // touchstart and touchmove event is 250ms or more.
      // Note that we need to explicitly prevent scrolling
      // after we start the drag delayed.
      if (e.pointerType === 'touch') {
        // On first event (touchstart) we need to store the
        // drag start data and bind listeners for touchmove
        // and contextmenu.
        if (e.isFirst) {
          const contextMenuListener = e => e.preventDefault();
          const touchMoveListener = (e) => {
            const data = dragStartData.get(item);
            if (data) {
              if (data.dragAllowed) {
                e.cancelable && e.preventDefault();
              } else if (data.dragAllowed === undefined) {
                if (e.cancelable && e.timeStamp - data.startTimeStamp > 250) {
                  data.dragAllowed = true;
                  e.preventDefault();
                } else {
                  data.dragAllowed = false;
                }
              } 
            }
          };
        
          // Store drag start data.
          dragStartData.set(item, {
            dragAllowed: undefined,
            startTimeStamp: e.srcEvent.timeStamp,
            touchMoveListener,
            contextMenuListener
          });
 
          // We need to bind the touch move listener to every scrollable ancestor
          // of the dragged item. You probably want to create a method for
          // querying such elements, but in this example we know the specific
          // elements so we explicitly define the listeners for those.
          // Also note that it's important to bind the listeners with
          // capture:true and passive:false options.
          container.parentNode.addEventListener('touchmove', touchMoveListener, { passive: false, capture: true });
          window.addEventListener('touchmove', touchMoveListener, { passive: false, capture: true });
          
          // Prevent context menu popping up.
          item.getElement().addEventListener('contextmenu', contextMenuListener);

          // Let's keep the drag start predicate in "pending" state.
          return undefined;
        }
        
        // On final event (touchend/touchcancel) we just need to
        // remove the listeners and delete the item's drag data.
        if (e.isFinal) {
          const data = dragStartData.get(item);
          if (data) {
            container.parentNode.removeEventListener('touchmove', data.touchMoveListener, { passive: false, capture: true });
            window.removeEventListener('touchmove', data.touchMoveListener, { passive: false, capture: true });
            item.getElement().removeEventListener('contextmenu', data.contextMenuListener);
            dragStartData.delete(item);
          }
          return undefined;
        }
        
        // On move (touchmove) event let's check the drag state from
        // our drag data and return it for the predicate.
        const data = dragStartData.get(item);
        return data ? data.dragAllowed : undefined;
      }
 
      // On mouse let's allow starting drag immediately
      // if mouse's left button is pressed down.
      if (e.isFirst && e.srcEvent.button) {
        return false;
      } else {
        return true; 
      }
    },
    dragAutoScroll: {
      targets: (item) => {
        return [
          { element: window, priority: 0 },
          { element: item.getGrid().getElement().parentNode, priority: 1 },
        ];
      }
    },
  })
  .on('dragInit', function (item) {
    item.getElement().style.width = item.getWidth() + 'px';
    item.getElement().style.height = item.getHeight() + 'px';
  })
  .on('dragReleaseEnd', function (item) {
    item.getElement().style.width = '';
    item.getElement().style.height = '';
    item.getGrid().refreshItems([item]);
  })
  .on('layoutStart', function () {
    if (boardGrid) {
      boardGrid.refreshItems().layout(); 
    }
  });

  columnGrids.push(grid);
});

// Init board grid so we can drag those columns around.
boardGrid = new Muuri('.board', {
  dragEnabled: true,
  dragHandle: '.board-column-header'
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/web-animations/2.3.2/web-animations.min.js
  2. https://cdn.jsdelivr.net/gh/haltu/muuri@0.9.5/dist/muuri.min.js