Create a Draggable Element in JavaScript


#42

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.


#43

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


#44

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:


#45

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


#46

Glad to hear it!

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