<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'
});
This Pen doesn't use any external CSS resources.