Create a Draggable Element in JavaScript

Figured it out, thank you! Again! I figured out it was because I was using the .element img {} to scale it within the browser window, but I guess it then scaled within the div instead? So I moved the max-width and width: 60% to .element {}, and in .element img {width: 100%;}. Now it works! I also added some code that prevent the images from being highlighted when dragged. Now it looks like this in the CSS file:
.element {
max-width: 363px;
width: 60%;
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
}

.element img {
pointer-events: none;
width: 100%;
}

.element:active {
opacity: .75;
}

.element:hover {
cursor: pointer;
}

Thanks again, you’re a real life saver! Now my webpage is finally ready to be launched :smiley:

1 Like

That is awesome! Glad to hear you are unblocked :slight_smile:

Hi there, I’m trying your code and for some reason it doesn’t get past the condition for the if statement in dragStart.

if(e.target === detailPanel) {
active = true;
}

detailPanel being my dragItem.

I’m trying to drag an element (toggled on the draggable in the html attributes) which has a few things inside. It’s basically a card, or as the name implies, a detail panel with lots of info in it.

If I console log out my detailPanel variable it does select the right element so I’m not sure why the script can’t get into the if statement.

var detailPanel = document.getElementsByClassName('detailTabWrappper');
console.log(detailPanel);
var panelContainer = document.body;
console.log(panelContainer);
var active = false;

var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;

panelContainer.addEventListener('mousedown', dragStart);
panelContainer.addEventListener('mouseup', dragEnd);
panelContainer.addEventListener('mousemove', drag);

function dragStart(e) {
  if(e.type === 'mousedown') {
    initialX = e.clientX - xOffset;
    initialY = e.clientY - yOffset;
  }

  // this here is not working
  if(e.target === detailPanel) {
    active = true;
  }
}

<article class="detailTabWrappper" draggable="true"></article>

Any ideas why?

One thing is that getElementsByClassName returns an array. Try this:

var detailPanel = document.getElementsByClassName('detailTabWrappper')[0];

That will ensure detailPanel points to an element instead of the array. Does that fix it?

Cheers,
Kirupa :stuck_out_tongue:

It works.

I can’t believe I got owned by that a second time. That the getEByClass returns an array.

Thanks!

1 Like

Thanks Kirupa for your post on drag and drop in javascript. It really helped me to learn about multiple drag and drop elements.

1 Like

Thanks for the comment! Glad the tutorial helped :slight_smile:

I’m trying to create a drag and select around multiple countries in a svg of the world.
The box should resize and grab user selected countries. tia, David
http://datamaps.github.io/

HELP
It runs, not clean, and needs fixes.
Can someone suggest how to fix the reset to (0,0) bug on reload+dragStart.
(https://codepen.io/Danny1897/pen/LaYBxK)

How do I write a sortable component, based on an items position property?

I am creating a Tangram game for children’s therapy sessions. I need to drag shapes on the page and rotate them in 22.5 deg increments to make the tangram images. I have the drag code (from your tutorial) done but have been unable to rotate the shapes to fit patterns that will be loaded by the user onto the page. Tangram so far can be seen at www.hansondesignmaine.com. Thank you for any help you can provide. Jack

Correction to web page name it is: www.hansondesignmaine.com/tangram/tangram.html Thanks, Jack

What gesture are you planning to use to rotate the element? The rotation is something that can be done by using a rotate CSS transform.

The game should be for mouse and touch as in your drag tutorial. I am assuming that touch or click plus drag would move the shape (existing code), whereas touch or click plus lift the pointer would rotate the shape. So you move the shape over the pattern then rotate the shape so it will fit in place and then move the shape to final position. The pattern will be a white outline that can be selected from a group of patterns on the right side of the screen and then appear in the center work space. I am suggesting a rotation of 22.5 degree increments so that the kids have more options to make their own objects if they don’t want to use a pattern. Separating the drag and rotate functions with similar pointer actions is a problem. The users will be occupational therapy patients, parents, and therapists. How would the CSS rotate transform be incorporated into the existing code and identify the existing items? Thanks, Jack

This is the part I was worried about, but I think we can differentiate between a drag and click. Check out the following snippet:

<!DOCTYPE html>
<html>

<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
  <title>Drag Multiple Images</title>
  <link href="https://www.kirupa.com/ssi/newDesign/kirupa_html5.css" rel="stylesheet" type="text/css">
  <style>
    body {
      margin: 20px;
    }

    #container {
      width: 600px;
      height: 400px;
      background-color: #EEE;
      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;
    }

    .item img {
      pointer-events: none;
    }

    .one {
      width: 100px;
      height: 100px;
      top: 0px;
      left: 0px;
    }

    .two {
      width: 60px;
      height: 60px;
      top: 30%;
      left: 10%;
    }

    .three {
      width: 40px;
      height: 40px;
      top: -40%;
      left: -10%;
    }

    .four {
      width: 80px;
      height: 80px;
      top: -10%;
      left: 5%;
    }

    .item:active {
      opacity: .75;
    }

    .item:hover {
      cursor: pointer;
    }

    h1 {
      margin-bottom: 10px;
    }
  </style>
</head>

<body>
  <h1>Drag Multiple Images</h1>
  <div id="outerContainer">
    <div id="container">
      <div class="item one">
        <img src="https://www.kirupa.com/icon/1f6a7.svg">
      </div>
      <div class="item two">
        <img src="https://www.kirupa.com/icon/1f680.svg">
      </div>
      <div class="item three">
        <img src="https://www.kirupa.com/icon/1f35d.svg">
      </div>
      <div class="item four">
        <img src="https://www.kirupa.com/icon/1f5fa.svg">
      </div>
    </div>
    <br>
    <p class="callout">Check out the <a href="https://www.kirupa.com/html5/drag.htm" class="blueEmphasis">tutorial</a>
      or the <a href="https://forum.kirupa.com/t/create-a-draggable-element-in-javascript/638149/3" class="blueEmphasis">discussion</a>
      around this effect.</p>
  </div>
  <script>
    var container = document.querySelector("#container");
    var activeItem = null;

    var active = false;
    var dragging = 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);

    container.addEventListener("click", rotateElement, false);

    function dragStart(e) {
      if (e.target !== e.currentTarget) {
        active = true;
        dragging = false;

        // this is the item we are interacting with
        activeItem = e.target;

        if (activeItem.degrees === undefined) {
          activeItem.degrees = 0;
        }

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

        dragging = true;

        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 rotateElement(e) {
      if (dragging === false) {

        if (activeItem === null) {
          activeItem = e.target;
        }

        console.log(activeItem.degrees);

        activeItem.degrees += 22.5;
        
        activeItem.querySelector("img").style.transform = `rotate(${activeItem.degrees}deg)`;
      }
    }

    function setTranslate(xPos, yPos, el) {
      el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
      //el.querySelector("img").style.transform = `rotate(${degrees}deg)`;
    }
  </script>
</body>

</html>

Does this work for your needs? The way this example work is by using a dragging variable to differentiate between a regular click and a drag operation. If the click was indeed a click, I rotate the child image by 22.5 degrees. The repositioning logic deals with the parent element, and the rotation deals with the child image element. This is a silly trick dealing with how transforms work and figuring out a way to set two different transform values.

:running_man:

Your solution works great! How can I thank you? Jack

Glad to hear it!

Help someone else out one day who has a strange technical problem :stuck_out_tongue:

Kirupa, My previous problem was with dragging and rotating shapes in a Tangram game. That can now be done, but when dragging the user must be careful not to accidentally rotate the shape when stopping drag. Also when dragging the user must have the cursor on the shape when they stop dragging. If cursor is not on the shape when they click or lift finger to stop dragging, the dragging action is not stopped (though the shape may not be moving at the moment) when they move the cursor near the shape it starts moving again without any new click, and until they get the cursor on the shape (not easy on small shapes) they cannot stop the dragging function. “Make a Face” (a therapy program I am doing to help children identify and locate facial parts) has the same problems. Any help would be appreciated. The problems can be easily seen by viewing https://www.therapygames.online/face/face.html and trying to move a face component rapidly. The part does not always stay with the cursor. Thanks, Jack

Also above does not appear to be a problem using a finger on a touch pad but is a problem on a touch pad using a mouse. Thanks, Jack

I totally see the problem, but I’m a bit swamped to look into this right now. I’ll do my best to get back to you on this soon. This might be a really simple fix or something that will involve some complicated tricks, and I don’t have a guess on where in that range the right answer will fall :ambulance: