<body>
<div id="app">
<div class="row">
<div class="panel">
<h3 class="title">
panel1
</h3>
<div class="body" draggable="false">
各 panel をドラッグ操作で動かすことができるサンプルです。<br>
任意の panel をドラッグ、要素の境界にドロップすると、その位置に panel を移動できます。
</div>
</div>
<div class="panel">
<h3 class="title">
panel2
</h3>
<div class="body">
This is a sample what the panels can be moved by dragging.
</div>
</div>
</div>
<div class="row">
<div class="panel">
<h3 class="title">
panel3
</h3>
<div class="body">
<span style="font-size:xx-large">🍣</span>
</div>
</div>
</div>
<div class="row">
<div class="panel">
<h3 class="title">
panel4
</h3>
<div class="body">
The implementation is not efficient. Paddings are deleted and re-created each time the panel is moved.
</div>
</div>
</div>
</div>
</body>
body {
background-color: #111;
font-family: sans-serif;
}
#app {
border: 2px solid #aaa;
background-color: #333;
}
.panel {
background-color: #38c;
flex: 1;
display: flex;
flex-direction: column;
border-radius: 6px;
}
.panel .title {
background-color: #ddd;
padding: 15px;
margin: 0px 0px 3px;
flex: 0;
}
.panel .body {
background-color: #8cc;
padding: 15px;
flex: 1;
}
.row {
display: flex;
flex-direction: row;
gap: 0;
}
.padding {
padding: 5px;
flex: 0;
}
.padding.active {
background-color: #f33;
}
let g_dragging_element = null;
function createRow() {
const elem = document.createElement("div");
elem.className = "row";
return elem;
}
function createPadding() {
const elem = document.createElement("div");
elem.className = "padding";
elem.addEventListener("dragenter", (e) => {
e.target.classList.add("active");
});
elem.addEventListener("dragleave", (e) => {
e.target.classList.remove("active");
});
elem.addEventListener("dragover", (e) => {
e.dataTransfer.dropEffect = "move";
e.preventDefault();
});
elem.addEventListener("drop", (e) => {
e.preventDefault();
if (g_dragging_element === null) return;
const panel = g_dragging_element;
g_dragging_element = null;
movePanel(panel, event.target);
});
return elem;
}
function movePanel(panel, destination) {
const destParent = destination.parentElement;
if (destParent.classList.contains("row")) {
destParent.insertBefore(panel, destination);
} else {
const newRow = createRow();
destParent.insertBefore(newRow, destination);
newRow.appendChild(panel);
}
resetAll();
setupAll();
}
function resetAll() {
document.querySelectorAll(".padding").forEach((e) => e.remove());
document
.querySelectorAll(".row")
.forEach((e) => e.children.length === 0 && e.remove());
}
function setupPanel(elem) {
if (elem.draggable === true || elem.draggable === "true") {
return;
}
elem.addEventListener("drag", undefined);
elem.addEventListener("dragstart", (e) => {
g_dragging_element = e.target;
});
elem.addEventListener("dragend", () => {
g_dragging_element = null;
});
elem.draggable = "true";
}
function setupRow(elem) {
const panels = elem.querySelectorAll(":scope > .panel");
for (const panel of panels) {
elem.insertBefore(createPadding(), panel);
setupPanel(panel);
}
elem.appendChild(createPadding());
}
function setupRoot(elem) {
const rows = elem.querySelectorAll(":scope > .row");
for (const row of rows) {
elem.insertBefore(createPadding(), row);
setupRow(row);
}
elem.appendChild(createPadding());
}
function setupAll() {
const root = document.querySelector("#app");
setupRoot(root);
}
window.addEventListener("DOMContentLoaded", () => {
setupAll();
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.