I am doing a project where images in a 3×3 grid are re-arranged by drag and drop.
When an image is dragged and dropped over another, they swap positions and I have got that part working, but I also need a line of text at the bottom showing how the images have been re-ordered. So if I swap the items 1 and 5 the text would no longer read: 1,2,3,4,5,6,7,8,9 but instead it should look like: 5,2,3,4,1,6,7,8,9.
I am really struggling with how to print the new numbers order after the items swap. Here is how it looks (the colors of the boxes were chosen randomly and have no importance):
This is my code:
//IMAGE ORDER
let order = [1, 2, 3, 4, 5, 6, 7, 8, 9]
function showOrder(order) {
document.getElementById('order').textContent = "Order: " + order.toString();
}
showOrder(order);
//DRAG AND DROP SWAP
var draggedImage = null;
var items;
function dragStart(e) {
draggedImage = this;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("item", this.innerHTML);
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
return false;
}
function dragEnter(e) {
this.classList.add("dragover");
}
function dragLeave(e) {
this.classList.remove("dragover");
}
function drop(e) {
e.stopPropagation();
if (draggedImage != this) {
draggedImage.innerHTML = this.innerHTML;
draggedImage.setAttribute("data-item", this.innerHTML);
let replacedImage = e.dataTransfer.getData("item");
this.innerHTML = replacedImage;
this.setAttribute("data-item", replacedImage);
}
return false;
}
function dragEnd(e) {
items.forEach(function(item) {
item.classList.remove("dragover");
});
}
document.addEventListener("DOMContentLoaded", event => {
items = document.querySelectorAll(".container .image");
items.forEach(function(item) {
item.addEventListener("dragstart", dragStart);
item.addEventListener("dragenter", dragEnter);
item.addEventListener("dragover", dragOver);
item.addEventListener("dragleave", dragLeave);
item.addEventListener("drop", drop);
item.addEventListener("dragend", dragEnd);
});
});
.container {
width: 330px;
height: 330px;
background-color: lightgrey;
display: flex;
flex-wrap: wrap;
}
.image {
width: 100px;
height: 100px;
background-color: #fff5d7;
text-align: center;
line-height: 100px;
margin: 5px;
cursor: move;
}
.order {
width: 330px;
height: 50px;
background-color: lightgrey;
margin-top: 10px;
text-align: center;
line-height: 50px;
font-size: x-large;
font-family: Helvetica;
}
<div class="container">
<div draggable="true" class="image" id="1"><img src="Dummy billeder/nummer 1.png" width="100px"></div>
<div draggable="true" class="image" id="2"><img src="Dummy billeder/nummer 2.png" width="100px"></div>
<div draggable="true" class="image" id="3"><img src="Dummy billeder/nummer 3.png" width="100px"></div>
<div draggable="true" class="image" id="4"><img src="Dummy billeder/nummer 4.png" width="100px"></div>
<div draggable="true" class="image" id="5"><img src="Dummy billeder/nummer 5.png" width="100px"></div>
<div draggable="true" class="image" id="6"><img src="Dummy billeder/nummer 6.png" width="100px"></div>
<div draggable="true" class="image" id="7"><img src="Dummy billeder/nummer 7.png" width="100px"></div>
<div draggable="true" class="image" id="8"><img src="Dummy billeder/nummer 8.png" width="100px"></div>
<div draggable="true" class="image" id="9"><img src="Dummy billeder/nummer 9.png" width="100px"></div>
</div>
<div id="order"></div>
I am doing a project where images in a 3×3 grid are re-arranged by drag and drop.
When an image is dragged and dropped over another, they swap positions and I have got that part working, but I also need a line of text at the bottom showing how the images have been re-ordered. So if I swap the items 1 and 5 the text would no longer read: 1,2,3,4,5,6,7,8,9 but instead it should look like: 5,2,3,4,1,6,7,8,9.
I am really struggling with how to print the new numbers order after the items swap. Here is how it looks (the colors of the boxes were chosen randomly and have no importance):
This is my code:
//IMAGE ORDER
let order = [1, 2, 3, 4, 5, 6, 7, 8, 9]
function showOrder(order) {
document.getElementById('order').textContent = "Order: " + order.toString();
}
showOrder(order);
//DRAG AND DROP SWAP
var draggedImage = null;
var items;
function dragStart(e) {
draggedImage = this;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("item", this.innerHTML);
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
return false;
}
function dragEnter(e) {
this.classList.add("dragover");
}
function dragLeave(e) {
this.classList.remove("dragover");
}
function drop(e) {
e.stopPropagation();
if (draggedImage != this) {
draggedImage.innerHTML = this.innerHTML;
draggedImage.setAttribute("data-item", this.innerHTML);
let replacedImage = e.dataTransfer.getData("item");
this.innerHTML = replacedImage;
this.setAttribute("data-item", replacedImage);
}
return false;
}
function dragEnd(e) {
items.forEach(function(item) {
item.classList.remove("dragover");
});
}
document.addEventListener("DOMContentLoaded", event => {
items = document.querySelectorAll(".container .image");
items.forEach(function(item) {
item.addEventListener("dragstart", dragStart);
item.addEventListener("dragenter", dragEnter);
item.addEventListener("dragover", dragOver);
item.addEventListener("dragleave", dragLeave);
item.addEventListener("drop", drop);
item.addEventListener("dragend", dragEnd);
});
});
.container {
width: 330px;
height: 330px;
background-color: lightgrey;
display: flex;
flex-wrap: wrap;
}
.image {
width: 100px;
height: 100px;
background-color: #fff5d7;
text-align: center;
line-height: 100px;
margin: 5px;
cursor: move;
}
.order {
width: 330px;
height: 50px;
background-color: lightgrey;
margin-top: 10px;
text-align: center;
line-height: 50px;
font-size: x-large;
font-family: Helvetica;
}
<div class="container">
<div draggable="true" class="image" id="1"><img src="Dummy billeder/nummer 1.png" width="100px"></div>
<div draggable="true" class="image" id="2"><img src="Dummy billeder/nummer 2.png" width="100px"></div>
<div draggable="true" class="image" id="3"><img src="Dummy billeder/nummer 3.png" width="100px"></div>
<div draggable="true" class="image" id="4"><img src="Dummy billeder/nummer 4.png" width="100px"></div>
<div draggable="true" class="image" id="5"><img src="Dummy billeder/nummer 5.png" width="100px"></div>
<div draggable="true" class="image" id="6"><img src="Dummy billeder/nummer 6.png" width="100px"></div>
<div draggable="true" class="image" id="7"><img src="Dummy billeder/nummer 7.png" width="100px"></div>
<div draggable="true" class="image" id="8"><img src="Dummy billeder/nummer 8.png" width="100px"></div>
<div draggable="true" class="image" id="9"><img src="Dummy billeder/nummer 9.png" width="100px"></div>
</div>
<div id="order"></div>
I have tried finding a way to indentify the position of the two images in the grid and then swapping those numbers in an array, when an image is dropped but I could not get it to work.
Share Improve this question edited Jul 2, 2023 at 18:27 Roko C. Buljan 207k41 gold badges328 silver badges340 bronze badges asked Jul 2, 2023 at 15:09 frameforgeframeforge 351 silver badge5 bronze badges 3- 1 Would be easier to share some code so that one trying to help you has some starting point. Images are wele for context, but you should also edit to create a minimal reproducible example. please read How to Ask. – Roko C. Buljan Commented Jul 2, 2023 at 15:17
- 1 Updated the answer with a really cool solution, don't forget to upvote (the arrow up), thanks! – Alexander Nenashev Commented Jul 2, 2023 at 16:22
- @RokoC.Buljan check my latest solution in the answer. a reactive one with Proxy, seems like you wanted it to look – Alexander Nenashev Commented Jul 2, 2023 at 16:23
2 Answers
Reset to default 4Here's a simpler and cleaner example/suggestion in where:
- Store index in
data
atrribute - Retrieve the indexes of the dragged and target drop elements
- Swap the elements in the DOM
- Update the array given those two indexes
- Print the order array
- Use delegated events (assigned on the wrapper
.grid
element)
const elGrid = document.querySelector(".grid");
const elOrder = document.querySelector("#order");
const order = [...elGrid.children].map(el => el.dataset.index);
let elDrag;
const showOrder = () => elOrder.textContent = `Order: ${order}`;
const events = {
dragstart() { elDrag = this; },
dragover(ev) { ev.preventDefault(); },
drop() {
if (elDrag === this) return;
const ia = [...elGrid.children].indexOf(elDrag);
const ib = [...elGrid.children].indexOf(this);
elDrag.replaceWith(this.cloneNode(true));
this.replaceWith(elDrag);
[order[ia], order[ib]] = [order[ib], order[ia]];
showOrder();
}
};
[...elGrid.children].forEach((el, i) => el.draggable = true);
["dragstart", "dragover", "drop"].forEach(evName => {
elGrid.addEventListener(evName, (ev) => {
const elItem = ev.target.closest(".item");
if (!elItem) return;
events[evName].call(elItem, ev);
});
});
showOrder();
.grid {
display: inline-grid;
grid-template-columns: repeat(3, 1fr);
background-color: #d3d3d3;
gap: 0.3rem;
padding: 0.3rem;
}
.item {
display: inline-flex;
background-color: #fffac2;
padding: 0.3rem;
}
<div class="grid">
<div class="item" data-index="1"><img src="https://placehold.co/40x40/0bf/fff?text=1" alt="1"></div>
<div class="item" data-index="2"><img src="https://placehold.co/40x40/0fb/fff?text=2" alt="2"></div>
<div class="item" data-index="3"><img src="https://placehold.co/40x40/bf0/fff?text=3" alt="3"></div>
<div class="item" data-index="4"><img src="https://placehold.co/40x40/b0f/fff?text=4" alt="4"></div>
<div class="item" data-index="5"><img src="https://placehold.co/40x40/fb0/fff?text=5" alt="5"></div>
<div class="item" data-index="6"><img src="https://placehold.co/40x40/f0b/fff?text=6" alt="6"></div>
<div class="item" data-index="7"><img src="https://placehold.co/40x40/8f0/fff?text=7" alt="7"></div>
<div class="item" data-index="8"><img src="https://placehold.co/40x40/f80/fff?text=8" alt="8"></div>
<div class="item" data-index="9"><img src="https://placehold.co/40x40/08f/fff?text=9" alt="9"></div>
</div>
<div id="order"></div>
Copy the IDs to your draggable content () and update the order box on the drop.
document.querySelectorAll('.container .image').forEach(div => {
div.querySelector('img').dataset.id = div.id;
});
const updateOrder = () => {
document.getElementById('order').textContent = 'Order: ' +
[...document.querySelectorAll('.container .image img')].map(img => img.dataset.id).join(', ');
};
updateOrder();
//DRAG AND DROP SWAP
var draggedImage = null;
var items;
function dragStart(e) {
draggedImage = this;
e.dataTransfer.effectAllowed="move";
e.dataTransfer.setData("item", this.innerHTML);
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
return false;
}
function dragEnter(e) {
this.classList.add("dragover");
}
function dragLeave(e) {
this.classList.remove("dragover");
}
function drop(e) {
e.stopPropagation();
if (draggedImage != this) {
draggedImage.innerHTML = this.innerHTML;
draggedImage.setAttribute("data-item", this.innerHTML);
let replacedImage = e.dataTransfer.getData("item");
this.innerHTML = replacedImage;
this.setAttribute("data-item", replacedImage);
updateOrder();
}
return false;
}
function dragEnd(e) {
items.forEach(function(item) {
item.classList.remove("dragover");
});
}
document.addEventListener("DOMContentLoaded", event => {
items = document.querySelectorAll(".container .image");
items.forEach(function(item) {
item.addEventListener("dragstart", dragStart);
item.addEventListener("dragenter", dragEnter);
item.addEventListener("dragover", dragOver);
item.addEventListener("dragleave", dragLeave);
item.addEventListener("drop", drop);
item.addEventListener("dragend", dragEnd);
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My Website</title>
<link rel="stylesheet" href="./style.css">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<style>
.container {
width: 330px;
height: 330px;
background-color: lightgrey;
display: flex;
flex-wrap: wrap;
}
.image {
width: 100px;
height: 100px;
background-color: #fff5d7;
text-align: center;
line-height: 100px;
margin: 5px;
cursor: move;
}
.order {
width: 330px;
height: 50px;
background-color: lightgrey;
margin-top: 10px;
text-align: center;
line-height: 50px;
font-size:x-large;
font-family: Helvetica;
}
</style>
</head>
<body>
<h1>Image re-ordering</h1>
<h2>Drag and drop to swap images</h2>
<div class="container">
<div draggable="true" class="image" id="1"><img src="Dummy billeder/nummer 1.png" width="100px"></div>
<div draggable="true" class="image" id="2"><img src="Dummy billeder/nummer 2.png" width="100px"></div>
<div draggable="true" class="image" id="3"><img src="Dummy billeder/nummer 3.png" width="100px"></div>
<div draggable="true" class="image" id="4"><img src="Dummy billeder/nummer 4.png" width="100px"></div>
<div draggable="true" class="image" id="5"><img src="Dummy billeder/nummer 5.png" width="100px"></div>
<div draggable="true" class="image" id="6"><img src="Dummy billeder/nummer 6.png" width="100px"></div>
<div draggable="true" class="image" id="7"><img src="Dummy billeder/nummer 7.png" width="100px"></div>
<div draggable="true" class="image" id="8"><img src="Dummy billeder/nummer 8.png" width="100px"></div>
<div draggable="true" class="image" id="9"><img src="Dummy billeder/nummer 9.png" width="100px"></div>
</div>
<div class="order" id="order"></div>
<script src="gasflasker test.js"></script>
</body>
</html>
But a better approach would be to move the whole boxes:
const $container = document.querySelector('.container');
const updateOrder = () => {
document.getElementById('order').textContent = 'Order: ' +
[...document.querySelectorAll('.container .image')].map(img => img.id).join(', ');
};
updateOrder();
//DRAG AND DROP SWAP
var draggedImage = null;
var items;
function dragStart(e) {
draggedImage = this;
e.dataTransfer.effectAllowed="move";
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
return false;
}
function dragEnter(e) {
this.classList.add("dragover");
}
function dragLeave(e) {
this.classList.remove("dragover");
}
function drop(e) {
e.stopPropagation();
if (draggedImage != this) {
const span = document.createElement('span');
$container.insertBefore(span, draggedImage);
$container.insertBefore(draggedImage, this.nextSibling);
$container.insertBefore(this, span);
span.remove();
updateOrder();
}
}
function dragEnd(e) {
items.forEach(function(item) {
item.classList.remove("dragover");
});
}
document.addEventListener("DOMContentLoaded", event => {
items = document.querySelectorAll(".container .image");
items.forEach(function(item) {
item.addEventListener("dragstart", dragStart);
item.addEventListener("dragenter", dragEnter);
item.addEventListener("dragover", dragOver);
item.addEventListener("dragleave", dragLeave);
item.addEventListener("drop", drop);
item.addEventListener("dragend", dragEnd);
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My Website</title>
<link rel="stylesheet" href="./style.css">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<style>
.container {
width: 330px;
height: 330px;
background-color: lightgrey;
display: flex;
flex-wrap: wrap;
}
.image {
width: 100px;
height: 100px;
background-color: #fff5d7;
text-align: center;
line-height: 100px;
margin: 5px;
cursor: move;
}
.order {
width: 330px;
height: 50px;
background-color: lightgrey;
margin-top: 10px;
text-align: center;
line-height: 50px;
font-size:x-large;
font-family: Helvetica;
}
</style>
</head>
<body>
<h1>Image re-ordering</h1>
<h2>Drag and drop to swap images</h2>
<div class="container">
<div draggable="true" class="image" id="1"><img src="Dummy billeder/nummer 1.png" width="100px"></div>
<div draggable="true" class="image" id="2"><img src="Dummy billeder/nummer 2.png" width="100px"></div>
<div draggable="true" class="image" id="3"><img src="Dummy billeder/nummer 3.png" width="100px"></div>
<div draggable="true" class="image" id="4"><img src="Dummy billeder/nummer 4.png" width="100px"></div>
<div draggable="true" class="image" id="5"><img src="Dummy billeder/nummer 5.png" width="100px"></div>
<div draggable="true" class="image" id="6"><img src="Dummy billeder/nummer 6.png" width="100px"></div>
<div draggable="true" class="image" id="7"><img src="Dummy billeder/nummer 7.png" width="100px"></div>
<div draggable="true" class="image" id="8"><img src="Dummy billeder/nummer 8.png" width="100px"></div>
<div draggable="true" class="image" id="9"><img src="Dummy billeder/nummer 9.png" width="100px"></div>
</div>
<div class="order" id="order"></div>
<script src="gasflasker test.js"></script>
</body>
</html>
If you want to go really cool, here's a reactive approach without manipulating DOM manually. So you create an array from the images' IDs and manipulate it through Proxy
. Sort the images in DOM on the array's changes. That way any additional functionality can be implemented easily, like a reset button:
<button onclick="ids.sort((a,b)=>a-b)">Reset</button>
const $container = document.querySelector('.container');
let updatePromise;
const ids = new Proxy([], {
set(target, prop, val){
// postpone the update with a microtask
updatePromise ??= Promise.resolve().then(() => {
updatePromise = null;
document.getElementById('order').textContent = 'Order: ' +
target.join(', ');
target.forEach(id => {
$container.appendChild(document.getElementById(id));
});
});
return Reflect.set(target, prop, val);
}
});
ids.push(...[...document.querySelectorAll('.container .image')].map(img => img.id));
let draggedImageId;
const events = {
dragstart: e => {
draggedImageId = e.target.id;
e.dataTransfer.effectAllowed = "move";
},
dragover: e => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
},
dragenter: e => {
const $image = e.target.closest('.image');
$image?.id === draggedImageId || $image?.classList.add("dragover");
},
dragleave: e => e.target.classList.remove("dragover"),
dragend: e => e.target.classList.remove("dragover"),
drop: e => {
if (e.target.id && draggedImageId !== e.target.id) {
$container.querySelector('.dragover')?.classList.remove('dragover');
const [from, to] = [ids.indexOf(draggedImageId), ids.indexOf(e.target.id)];
[ids[from], ids[to]] = [ids[to], ids[from]];
}
}
};
for(const name in events){
$container.addEventListener(name, events[name]);
}
body{
display:flex;
gap:20px;
}
.container {
width: 162px;
height: 164px;
background-color: lightgrey;
display: flex;
flex-wrap: wrap;
border: 2px solid lightgrey;
border-radius:3px;
}
.image.dragover{
background-color: yellow;
}
.image {
width: 50px;
height: 50px;
background-color: #fff5d7;
text-align: center;
line-height: 50px;
margin: 2px;
cursor: move;
border-radius:2px;
}
.order {
width: 220px;
height: 30;
background-color: lightgrey;
margin: 10px 0;
text-align: center;
line-height: 30px;
font-family: Helvetica;
}
<div class="container">
<div draggable="true" class="image" id="1">1</div>
<div draggable="true" class="image" id="2">2</div>
<div draggable="true" class="image" id="3">3</div>
<div draggable="true" class="image" id="4">4</div>
<div draggable="true" class="image" id="5">5</div>
<div draggable="true" class="image" id="6">6</div>
<div draggable="true" class="image" id="7">7</div>
<div draggable="true" class="image" id="8">8</div>
<div draggable="true" class="image" id="9">9</div>
</div>
<div>
<div class="order" id="order"></div>
<button onclick="ids.sort((a,b)=>a-b)">Reset</button>
</div>