Create a Draggable Element in JavaScript

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
3 Likes

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

1 Like

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 :frowning: :slight_smile:

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)

2 Likes

That is a great point. I’ll update the code momentarily :slight_smile:

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 :slight_smile:

1 Like

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 :stuck_out_tongue:

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.

1 Like

Can you post an example?

:slight_smile:

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 :slight_smile: