Create a Draggable Element in JavaScript


#1

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

#2

Hi,
How about if i want to have multiple draggable elements?
Thank you very much for your concerning.


#3

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


#4

Hi Kirupa,

Yes, it works pretty well.
This code really helps me a lot.
Thank you very much.

Best Regards,
Alvin


#5

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"){ ...

#6

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


#7

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)


#8

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


#9

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.


#10

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:


#11

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.


#12

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.


#13

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:


#14

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.


#15

Can you post an example?

:slight_smile:


#16

How can I transform it so I can move several elements on the page? Here is the code: https://pastebin.com/QV8UhHhC