How to Create a Drag and Drop Table with CSS and JavaScript
In this Javascript Tutorial, we will see how to create an interactive table where users can rearrange content (both text and images) with a simple drag and drop.
Project Source Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Draggable Mixed Content Table</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&
display=swap" rel="stylesheet">
<style>
:root{
--primary-color:#4a90ea;
--secondary-color:#f5f7fa;
--text-color:#333;
--border-color:#eaeaea;
--hover-color:#ecf0f1;
--shadow-color:rgba(0,0,0,0.1);
--gradient-bg:linear-gradient(135deg, #6dd5ed 0%, #2193b0 100%);
}
*{ box-sizing: border-box; margin: 0; padding: 0; }
body{
font-family: 'Poppins', sans-serif;
background-color: var(--secondary-color);
color: var(--text-color); display: flex;
justify-content: center; align-items: center;
min-height: 100vh; padding: 20px;
}
.table-container{
background-color: #fff; border-radius: 20px;
box-shadow: 12px 12px 24px var(--shadow-color),
-12px -12px 24px rgba(255,255,255,0.6);
overflow: hidden; transition: all 0.3s ease;
max-width: 900px; width: 100%; padding: 20px;
}
.table-container:hover{
box-shadow: 15px 15px 30px rgba(0,0,0,0.15),
-15px -15px 30px rgba(255,255,255,0.75);
}
table{ border-collapse: separate; border-spacing: 0;
width: 100%; border-radius: 12px; overflow: hidden;
}
th, td{ padding: 16px 24px; text-align: center; transition: all 0.3s ease; }
th{ background: var(--gradient-bg); color: white;
font-weight: 600; text-transform: uppercase;
letter-spacing: 1px; font-size: 14px;
}
tbody tr:last-child td{ border-bottom: none; }
td:hover{ background-color: var(--hover-color); }
.dragging{ opacity: 0.7; transform: scale(0.98); }
.drag-over{ background-color: var(--hover-color) !important;
transform: scale(1.05);
}
.drag-ghost{background-color: var(--primary-color);
color: white; border-radius: 4px;
padding: 8px 12px; font-size: 14px; position: fixed;
pointer-events: none; z-index: 1000; opacity: 0.9;
box-shadow: 0 4px 10px var(--shadow-color);
}
img{ max-width: 100px; max-height: 100px; object-fit: cover;
border-radius: 8px; transition: all 0.3s ease;
}
img:hover{ transform: scale(1.1);
box-shadow: 0 4px 8px var(--shadow-color);
}
/* Highlight animation for dropped items */
.highlight{
animation: highlight 1s ease-in-out;
}
@keyframes highlight{
0%{ background: var(--primary-color); color:white; }
100%{ background: transparent; color:var(--text-color); }
}
</style>
</head>
<body>
<div class="table-container">
<table id="myTable">
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<td draggable="true"><img src="images/avatar7.png"
alt="image1" draggable="false"></td>
<td draggable="true">Text Cell 1</td>
<td draggable="true"><img src="images/avatar2.png"
alt="image2" draggable="false"></td>
</tr>
<tr>
<td draggable="true">Text Cell 2</td>
<td draggable="true"><img src="images/avatar3.png"
alt="image3" draggable="false"></td>
<td draggable="true">Text Cell 3</td>
</tr>
<tr>
<td draggable="true"><img src="images/avatar4.png"
alt="image4" draggable="false"></td>
<td draggable="true">Text Cell 4</td>
<td draggable="true"><img src="images/avatar5.png"
alt="image5" draggable="false"></td>
</tr>
</tbody>
</table>
</div>
<script>
const table = document.getElementById('myTable');
let draggedCell = null;
let dragGhost = null;
// Event listener for drag start
table.addEventListener('dragstart', (e) => {
if(e.target.tagName !== 'TD') return;
draggedCell = e.target;
e.target.classList.add('dragging');
// Create a ghost element for dragging
dragGhost = document.createElement('div');
dragGhost.className = 'drag-ghost';
// Check if the dragged cell contains an image or text
const img = draggedCell.querySelector('img');
if(img){
dragGhost.innerHTML = `<img src="${img.src}"
alt="${img.alt}" style="width:50px; object-fit:cover;">`;
}
else{ dragGhost.textContent = draggedCell.textContent; }
document.body.appendChild(dragGhost);
e.dataTransfer.setDragImage(dragGhost, 25, 25);
setTimeout(() => { e.target.style.opacity = '0.5';}
, 0 );
});
// Event listener for drag over
table.addEventListener('dragover', (e) => {
e.preventDefault();
if(dragGhost){
dragGhost.style.left = e.clientX + 15 + 'px';
dragGhost.style.top = e.clientY + 15 + 'px';
}
});
// Event listener for drag enter
table.addEventListener('dragenter', (e) => {
e.preventDefault();
if(e.target.tagName === 'TD'){
e.target.classList.add('drag-over');
}
});
// Event listener for drag leave
table.addEventListener('dragleave', (e) => {
e.preventDefault();
if(e.target.tagName === 'TD'){
e.target.classList.remove('drag-over');
}
});
// Event listener for drop
table.addEventListener('drop', (e) => {
e.preventDefault();
if(e.target.tagName === 'TD'){
// Swap the contents of the cells
const temp = e.target.innerHTML;
e.target.innerHTML = draggedCell.innerHTML;
draggedCell.innerHTML = temp;
// Remove drag-over class and add highlight effect
e.target.classList.remove('drag-over');
e.target.classList.add('highlight');
draggedCell.classList.add('highlight');
// Remove highlight class after animation
setTimeout(() => { e.target.classList.remove('highlight');
draggedCell.classList.remove('highlight');
}, 1000);
}
});
// Event listener for drag end
table.addEventListener('dragend', (e) => {
e.target.classList.remove('dragging');
e.target.style.opacity = '1';
table.querySelectorAll('td').forEach(cell => {
cell.classList.remove('drag-over');
});
if(dragGhost){
document.body.removeChild(dragGhost);
dragGhost = null;
}
});
</script>
</body>
</html>
OUTPUT: