by kirupa | 26 April 2018
Have questions? Discuss this HTML5 / JavaScript tutorial with others on the forums.
This is a companion discussion topic for the original entry at http://www.kirupa.com/html5/drag.htm
by kirupa | 26 April 2018
Have questions? Discuss this HTML5 / JavaScript tutorial with others on the forums.
Hi,
How about if i want to have multiple draggable elements?
Thank you very much for your concerning.
Hi Alvin!
That will require adjusting the draggable element from being hard coded for a single element to handle an arbitrary number of elements. I used the ideas from the Handling Events for Many Elements article to make that adjustment.
The full code for being able to drag multiple elements looks as follows:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Drag Multiple Elements</title>
<style>
#container {
width: 100%;
height: 400px;
background-color: #333;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 7px;
touch-action: none;
}
.item {
border-radius: 50%;
touch-action: none;
user-select: none;
position: relative;
}
.one {
width: 100px;
height: 100px;
background-color: rgb(245, 230, 99);
border: 10px solid rgba(136, 136, 136, .5);
top: 0px;
left: 0px;
}
.two {
width: 60px;
height: 60px;
background-color: rgba(196, 241, 190, 1);
border: 10px solid rgba(136, 136, 136, .5);
top: 30%;
left: 10%;
}
.three {
width: 40px;
height: 40px;
background-color: rgb(0, 255, 231);
border: 10px solid rgba(136, 136, 136, .5);
top: -40%;
left: -10%;
}
.four {
width: 80px;
height: 80px;
background-color: rgb(233, 210, 244);
border: 10px solid rgba(136, 136, 136, .5);
top: -10%;
left: 5%;
}
.item:active {
opacity: .75;
}
.item:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div id="outerContainer">
<div id="container">
<div class="item one">
</div>
<div class="item two">
</div>
<div class="item three">
</div>
<div class="item four">
</div>
</div>
</div>
<script>
var container = document.querySelector("#container");
var activeItem = null;
var active = false;
container.addEventListener("touchstart", dragStart, false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
function dragStart(e) {
if (e.target !== e.currentTarget) {
active = true;
// this is the item we are interacting with
activeItem = e.target;
if (activeItem !== null) {
if (!activeItem.xOffset) {
activeItem.xOffset = 0;
}
if (!activeItem.yOffset) {
activeItem.yOffset = 0;
}
if (e.type === "touchstart") {
activeItem.initialX = e.touches[0].clientX - activeItem.xOffset;
activeItem.initialY = e.touches[0].clientY - activeItem.yOffset;
} else {
console.log("doing something!");
activeItem.initialX = e.clientX - activeItem.xOffset;
activeItem.initialY = e.clientY - activeItem.yOffset;
}
}
}
}
function dragEnd(e) {
if (activeItem !== null) {
activeItem.initialX = activeItem.currentX;
activeItem.initialY = activeItem.currentY;
}
active = false;
activeItem = null;
}
function drag(e) {
if (active) {
if (e.type === "touchmove") {
e.preventDefault();
activeItem.currentX = e.touches[0].clientX - activeItem.initialX;
activeItem.currentY = e.touches[0].clientY - activeItem.initialY;
} else {
activeItem.currentX = e.clientX - activeItem.initialX;
activeItem.currentY = e.clientY - activeItem.initialY;
}
activeItem.xOffset = activeItem.currentX;
activeItem.yOffset = activeItem.currentY;
setTranslate(activeItem.currentX, activeItem.currentY, activeItem);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
</script>
</body>
</html>
You can see a live demo of it here: https://www.kirupa.com/html5/examples/drag_multiple.htm
Let me know if this works for you.
Cheers,
Kirupa
Hi Kirupa,
Yes, it works pretty well.
This code really helps me a lot.
Thank you very much.
Best Regards,
Alvin
Quick note, you’ll want to move preventDefault out of the if statement or you will have problems…
if (e.type === "touchmove") {
e.preventDefault();
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
Should be:
e.preventDefault();
if(e.type === "touchmove"){ ...
@Joe_Bloggs - What are the problems? I tested it on my iPhone with the code as-is, and I’m able to drag the element around
The code is great! But you only have preventDefault for mobile, not for laptops or desktop browsers, so it will not work unless you move preventDefault() out of the “if” block of code it is currently in (where it is only testing for mobile devices)
That is a great point. I’ll update the code momentarily
Thanks for this. Works very well.
Do you have something that would detect collisions on dragEnd?
I am trying to only allow “dropping” if dropped in the proper place.
You may need to adapt that code with the Drag Drop API: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
There are many ways to fake this behavior by detecting whether your mouse cursor is hovering over the drop area. We can look at some of these fake approaches if you the “recommended” approach using the Drag Drop API doesn’t work out
Thanks Kirupa,
My impression is that the Drag & Drop API does not work with touch events.
I am designing for a 55" touch screen, like you might find in a mall or large office building.
I am trying with collision detection, but so far no luck.
If you could share a fake approach you might have come across, I would much appreciate it.
P.S. This is the resource that helped me to get it working:
Specifically:
function dragEnd(e) {
dropHandler(event.target, event.changedTouches[0].pageX, event.changedTouches[0].pageY);
}
function dropHandler(element, coordX, coordY){
if(element.parentNode.classList.contains(targetElement.id)){
// console.log('good drop');
dropped++;
}else{
//console.log('bad drop');
}
}
Plus the important note to disable pointer events when dragging to access the target element underneath.
.active{
pointer-events: none;
}
Thanks again.
That is awesome! The approach I was thinking is where we listen for a mouseover or touchover event that fires over the drop area. When that happens, we can treat the drop as having succeeded when you release the mouse or touch. I’m just thinking out loud here, so what I said could be completely wrong
Thanks for this posting. The code works great! But I’m having issues with the items going beyond the container border when using FireFox, Chrome and in my mobile. It does stop at the container border when using Microsoft Edge.
Is there a fix for this issue?
Again, thank you for this fourm.
Can you post an example?
How can I transform it so I can move several elements on the page? Here is the code: https://pastebin.com/QV8UhHhC
Omg this was a lifesaver, thank you so much!! It works great. I have many elements, and now it works like a charm. I only have one problem, I have to press the trackpad on my mac ones more to make it drop the element. It doesnt let go by itself. Do you have any clue how I can fix this problem?
Sorry for spamming, but I found what’s wrong, it’s that I’m using png files. Then it doesn’t work properly. Do you know how I can make it work using the img tag? I need it to be files I’ve already made in illustrator
Hi Kirupa,
Thank you for your effort. Is it possible to re-arrange the code with many elements to use “element.style.top” and “element.style.left”. Because I need to keep the absolute coordinates of the elements fo later restructuring.
Best regards.
Just place your image files inside the div
element that is currently the item that is being dragged around. The content inside it doesn’t matter at all
:: Copyright KIRUPA 2024 //--