<!-- You'd need to set up something different for touch devices though -->
<h1>Drag and drop example </h1>
<div class="wrap">
<div id='phrase'>
<!-- using a form will help enforce sensible entries -->
<form id='calculate-position'>
<label for='rowInput'>Enter a row A - H</label>
<input type='text' id='rowInput' pattern='[a-hA-H]' placeholder='Enter Row' required>
<label for='colInput'>Enter a column 1 - 12</label>
<input type='number' id='colInput' min='1' max='12' pattern='[0-9]' placeholder=' Enter Column' required>
<button id='calculate'>Calculate</button>
</form>
<output class='target'>The cell number is <b id='cellnum'>?</b></output>
<div id='words'>
<button type='button' id='move-text' type='button'>Move Text Content!</button>
<!-- remove whitespace from inside div html and then we can use :empty in css to change background -->
<div data-id='01'><span class='words' data-id='01'>H1 text</span></div>
<div data-id='02'><span class='words' data-id='02'>H2 text</span></div>
<div data-id='03'><span class='words' data-id='03'>H3 text</span></div>
<div data-id='04'><span class='words' data-id='04'>H4 text</span></div>
<div data-id='05'><span class='words' data-id='05'>H5 text</span></div>
<div data-id='06'><span class='words' data-id='06'>H6 text</span></div>
<div data-id='07'><span class='words' data-id='07'>G1 text</span></div>
<div data-id='08'><span class='words' data-id='08'>G2 text</span></div>
<div data-id='09'><span class='words' data-id='09'>G3 text</span></div>
<div data-id='10'><span class='words' data-id='10'>G4 text</span></div>
</div>
</div>
<div id="drop-em" class="grid">
<div class="item">
<div class="content"></div><!-- must have no spaces inside .content -->
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
<div class="item">
<div class="content"></div>
</div>
</div>
</div>
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
counter-reset: columnCount 1 alphaCount cellCount;
}
h1 {
text-align: center;
}
.wrap {
display: flex;
gap: 2rem;
position:relative;
padding-left:220px;
}
.grid {
margin: auto;
display: grid;
flex: 1 0 0;
grid-template-columns: repeat(12, 1fr);
padding-top: 1.5rem;
}
.item {
position: relative;
background-color: #f9f9f9;
border: 1px solid grey;
aspect-ratio: 1/1;
counter-increment: columnCount;
min-width: 0;
transition: background 1s ease;
}
.item:nth-of-type(12n + 1) {
counter-increment: alphaCount;
}
.item:nth-of-type(12n + 1)::before {
content: counter(alphaCount, upper-alpha);
position: absolute;
display: flex;
align-items: center;
top: 0;
bottom: 0;
left: -1.75rem;
color: red;
pointer-events: none;
}
.item:nth-of-type(n + 13)::after {
display: none;
}
.item::after {
content: counter(columnCount);
position: absolute;
left: 0;
right: 0;
text-align: center;
top: -1.75rem;
color: red;
pointer-events: none;
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
color: #000;
padding: 1rem;
word-wrap: break-word;
counter-increment: cellCount;
}
.words {
cursor: move;
transition: padding 0.5s ease;
}
.content:has(.ui-draggable-dragging) {
overflow: visible;
}
.ui-droppable-active .content {
overflow: visible;
}
.words.ui-draggable-dragging {
background: blue;
padding: 5px 10px;
border-radius: 6px;
z-index: 999;
color: #fff;
display: block;
width: 50px;
height: 50px;
overflow: hidden;
}
#phrase {
position:absolute;
left:0;
top:0;
bottom:0;
color: #fff;
width:150px;
z-index: 2;
margin:1rem 0 .5rem;
}
form#calculate-position, /* flex moved from #phase here */
#words {
display: flex;
flex-direction: column;
}
#words > div { /* changed id to #words */
margin: 0 0 10px;
width:150px;
padding: 5px 10px;
background: #007bff;
border: 2px solid #007bff;
border-radius: 6px;
color: #fff;
}
#words > div:empty { /* changed id to #words */
background: #fff;
border-style: dashed;
padding: 0px 25px;
min-height: 30px;
}
.moved {
animation: fade 3s ease;
}
@keyframes fade {
0% {
opacity: 0;
}
50% {
opacity: 1;
background: red;
}
}
.item .content::before {
content: counter(cellCount);
position: absolute;
top: 2px;
left: 2px;
font-size: smaller;
color: #666;
border-radius: 50%;
border: 1px solid red;
background: white;
width: 1.2rem;
height: 1, 2rem;
display: grid;
place-items: center;
}
#move-text.disabled{
cursor:none;
pointer-events:none;
opacity:0.5;
}
#phrase:has(.ui-droppable-active){
overflow:visible;
}
input,
button,
label,
output.target { /* changed to output */
display: block;
margin: 0 0 0.4rem;
color:#000;
}
output.target {margin:0 0 1rem} /* changed to output */
/*
Requires jquery + UI
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js
*/
$(".words").draggable({
revert: function (event, ui) {
var bRevertingPhrase = this.closest("#drop-em");
if (!bRevertingPhrase.length) {
var phraseID = $(this).data("id");
var phraseHomeID = $(this).parent().data("id");
//If the child and parent ids don't match, we move the child to the correct parent
if (phraseID !== phraseHomeID) {
var correctCell = $("#phrase").find("div[data-id='" + phraseID + "']");
correctCell.prepend(this);
}
}
return !event;
}
});
$("#drop-em > div").droppable({
drop: function (ev, ui) {
$(ui.draggable)
.detach()
.css({ top: 0, left: 0 })
.appendTo($(this).find(".content:empty"));
$("#move-text").addClass("disabled");
}
});
$("#phrase > div").droppable({
drop: function (ev, ui) {
$(ui.draggable).detach().css({ top: 0, left: 0 }).prependTo(this);
}
});
var reOrder = [];
function fill(startPos = 1) {
const cells = document.querySelectorAll('#words > div > span');
let newLoc;
let myIndex = 0;
cells.forEach((cell, index) => {
newLoc = document.querySelector(
`.item:nth-child(${reOrder[myIndex + startPos - 1]}) .content `,
);
newLoc.append(cell);
newLoc.classList.add('moved');
if (index + startPos - 1 === 95) {
startPos = 0;
myIndex = 0;
}
myIndex += 1;
});
}
function reArrange(limit1, limit2) {
for (let loop = 0; loop < 8; loop++) {
for (let i = 0; i < 6; i++) {
reOrder.push(limit1 + i);
}
limit1 = limit1 - 12;
}
for (let loop = 0; loop < 8; loop++) {
for (let j = 0; j < 6; j++) {
reOrder.push(limit2 + j);
}
limit2 = limit2 - 12;
}
}
reArrange(85, 91);
/* get cell number */
/**
* Calculates the row number based on the character provided, using a start value and step size.
* @param {string} char - The character to calculate the row number for.
* @param {number} [start=42] - The starting row number.
* @param {number} [step=-6] - The step size to increment/decrement for each character.
* @returns {number} The calculated row number based on the character provided.
*/
function calcRowFromChar(char, start = 42, step = -6) {
// e.g. 'a' → upperCase 'A' → charCode is 65 → minus 65 → 0
const charPosInAlphabet = char.toUpperCase().charCodeAt(0) - 65;
return start + charPosInAlphabet * step;
}
// A module pattern to keep startPos out of the
// global context and only where it is needed
function createHandlers() {
// startPos is local to createHandlers and only
// available to the returned handlers
let startPos = 1;
return {
calculateStart(event) {
// prevent actually submitting the form
event.preventDefault();
const start = 42
// we can access the form elements directly from the form, without querySelecting
const { rowInput, colInput } = event.target.elements;
const rowNum = calcRowFromChar(rowInput.value, start);
const colNum = Number(colInput.value);
startPos = (colNum < 7)
? rowNum + colNum
: rowNum + start + colNum;
document.querySelector('#cellnum').textContent = startPos;
},
moveToGrid(event) {
// disable button
event.target.classList.add('disabled');
// pass in startPos to fill function instead
fill(startPos);
}
};
}
const handlers = createHandlers();
const calculateButton = document.querySelector('#calculate-position');
calculateButton.addEventListener('submit', handlers.calculateStart);
const moveTextBtn = document.querySelector('#move-text');
moveTextBtn.addEventListener('click', handlers.moveToGrid, { once: true });
This Pen doesn't use any external CSS resources.