JSFiddle - React, Tailwind, and code Playground
by Imri Paloja
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
<div class="container">
<div id="editor-content" spellcheck="false" contenteditable="true">
<p draggable="true">Paragraph 1 – Lorem ipsum dolor sit amet.</p>
<p draggable="true">Paragraph 2 – Consectetur adipiscing elit.</p>
<p draggable="true">Paragraph 3 – Sed do eiusmod tempor.</p>
<p draggable="true">Paragraph 4 – Incididunt ut labore et dolore.</p>
<p draggable="true">Paragraph 5 – Magna aliqua.</p>
</div>
</div>
CSS
html,
body {
color: #454545;
}
.container {
margin-top: 5% !important;
}
/* ------------------------------------------------------------------
Layout of the editable area
------------------------------------------------------------------ */
#editor-content {
width: 600px;
margin: 2rem auto;
padding: 1rem;
border: 2px solid #ddd;
border-radius: 6px;
background: #fafafa;
line-height: 1.6;
font-family: Arial, sans-serif;
}
#editor-content p {
position: relative; /* anchor for the handle */
margin: 0.5rem 0;
padding-right: 2rem; /* space for the handle */
cursor: text; /* normal text cursor inside the paragraph */
}
/* ------------------------------------------------------------------
The draggable handle – a tiny <span> that lives at the right edge
------------------------------------------------------------------ */
.drag-handle {
position: absolute;
top: 50%;
right: 0.4rem;
transform: translateY(-50%);
width: 1.2rem;
height: 1.2rem;
cursor: grab; /* shows the “grab” cursor */
display: inline-block;
}
.drag-handle:after {
/* This draws a simple “≡” (three bars) icon */
content: "\2261";
font-size: 1.2rem;
color: #777;
line-height: 1;
display: block;
text-align: center;
}
.drag-handle.dragging {
opacity: 0.5;
cursor: grabbing;
}
/* Placeholder that appears while dragging */
.placeholder {
height: 0;
margin: 0.5rem 0;
border-top: 2px dashed #4a90e2;
}
JavaScript
/* ==============================================================
1️⃣ Helper: add a draggable handle to a single <p>
============================================================== */
function addHandleToParagraph(p) {
// Avoid adding duplicate handles
if (p.querySelector('.drag-handle')) return;
const handle = document.createElement('span');
handle.className = 'drag-handle';
handle.setAttribute('draggable', 'true');
p.appendChild(handle);
}
/* ==============================================================
2️⃣ Initialise: add handles to all existing paragraphs
============================================================== */
function initHandles() {
const container = document.getElementById('editor-content');
container.querySelectorAll('p').forEach(addHandleToParagraph);
}
/* ==============================================================
3️⃣ MutationObserver – watch for new <p> elements
============================================================== */
function observeNewParagraphs() {
const container = document.getElementById('editor-content');
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
// Directly added <p>
if (node.nodeType === Node.ELEMENT_NODE && node.matches('p')) {
addHandleToParagraph(node);
}
// Or a wrapper that contains <p> children
if (node.nodeType === Node.ELEMENT_NODE) {
node.querySelectorAll('p').forEach(addHandleToParagraph);
}
});
});
});
observer.observe(container, { childList: true, subtree: true });
}
/* ==============================================================
4️⃣ Drag‑and‑Drop logic (delegated on the container)
============================================================== */
function enableDnD() {
const container = document.getElementById('editor-content');
let...