Create a Draggable Element in JavaScript

You can use local storage: https://www.kirupa.com/things_to_do_with_data/web_storage.htm

Store the transform value for each element. When the page loads, read the stored transform value and apply it back to each element :slight_smile:

Hey thanks for that helpful info :smiley:

@kirupa Hi there! im looking to do exactly this, I have your multiple point setup and working, I am now looking to create lines to link them together that move with it, eg, will have 4 circles and connect circle a to b with a line and so on, 4 lines + 4 circles. Is this possible? ill keep looking for my own solution in the interim! thanks in advance

That is possible! It is much easier using the canvas, but there are ways of making that work on the DOM as well. I donā€™t have any code readily available, but I can share something with you in a little bit. The trick is identifying the element you want to use for the line. My current thinking is to use a div element whose height is the distance between two points, and its width is 1 or 2 pixels. This will essentially look like a line. This div elementā€™s size and rotation will vary based on where the current endpoint circle is being dragged.

:thinking:

Drag Multiple Elements [Help Required]
How to use local storage to retain final position of each token?
So on page reload tokens are placed as per storage data [Got that working]
Problem is token snaps back to default css position away from cursor on first interaction/touch!
Codepen Debug Page
ANY ASSISTANCE will be greatly appreciated - am thinking I am erring by storing translate3d values?

This is a great repro! Thanks for providing it. I donā€™t have a computer to check this on right now, but I think you may need to persist the values of xOffset and yOffset as well. Those two values start at 0 and adjust based on where the dragged item ends up, and they are responsible for keeping the internal state of the drag in-sync with the translate values that appear.

ā€¦thanks for the prompt reply - if possible, when you have time and computer access, if you could look at the Codepen page [your original code with my added local storage bits] and assist with the x & y Offsets - I feel I am close, yet ā€œno cigarā€ā€¦

Here you go:

<!DOCTYPE html>
<html lang="en">

<head>

  <style>
    #container {
      width: 600px;
      height: 400px;
      background-color: #333;
      display: flex;
      align-items: flex-start;
      justify-content: flex-start;
      overflow: hidden;
      border-radius: 7px;
      touch-action: none;
    }

    .item {
      border-radius: 50%;
      touch-action: none;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      font-weight: 700;
    }

    .one {
      width: 100px;
      height: 100px;
      background-color: rgb(245, 230, 99);
      border: 10px solid rgba(136, 136, 136, .5);
      top: 0px;
      left: 0px;
      font-size: x-large;
    }

    .two {
      width: 60px;
      height: 60px;
      background-color: rgba(196, 241, 190, 1);
      border: 10px solid rgba(136, 136, 136, .5);
      top: 0%;
      left: 30%;
    }

    .three {
      width: 40px;
      height: 40px;
      background-color: rgb(0, 255, 231);
      border: 10px solid rgba(136, 136, 136, .5);
      top: 0%;
      left: 50%;
    }

    .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;
    }

    h1 {
      margin-bottom: 10px;
    }

    h2 {
      color: red;
    }

    h3 {
      color: blue
    }
  </style>
</head>

<body translate="no">
  <h1>Drag Multiple Elements [Help?]</h1>
  <h2>How to use local storage to retain final position of each token?</h2>
  <p>So on page reload tokens are placed as per storage data [Got that working]</p>
  <h3>Problem is token snaps back to default css position away from cursor on first interaction/touch!</h3>
  <div id="outerContainer">
    <div id="container">
      <div class="item one">1</div>
      <div class="item two">2</div>
      <div class="item three">3</div>

    </div>
    <script id="rendered-js">
      //https://www.kirupa.com/html5/drag.htm
      //https://www.kirupa.com/html5/examples/drag_multiple.htm

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

      //STORE TOKEN POSITIONS MY ADD
      var elONE = document.querySelector("#container .one");
      var elTWO = document.querySelector("#container .two");
      var elTHREE = document.querySelector("#container .three");

      function storePositions() {
        //var positionONE = elONE.style.top;
        var positionONE = elONE.style.transform;
        localStorage.setItem('positionONE', positionONE);
        localStorage.setItem('positionONE_x', elONE.xOffset);
        localStorage.setItem('positionONE_y', elONE.yOffset);

        console.log("position ONE stored:" + positionONE);
        console.log(elONE.xOffset + " " + elONE.yOffset);

        var positionTWO = elTWO.style.transform;
        localStorage.setItem('positionTWO', positionTWO);
        localStorage.setItem('positionTWO_x', elTWO.xOffset);
        localStorage.setItem('positionTWO_y', elTWO.yOffset);

        var positionTHREE = elTHREE.style.transform;
        localStorage.setItem('positionTHREE', positionTHREE);
        localStorage.setItem('positionTHREE_x', elTHREE.xOffset);
        localStorage.setItem('positionTHREE_y', elTHREE.yOffset);
      };


      //RETRIVE TOKEN POSITIONS
      function retrievePositions() {
        var positionONEsaved = localStorage.getItem('positionONE');
        var positionTWOsaved = localStorage.getItem('positionTWO');
        var positionTHREEsaved = localStorage.getItem('positionTHREE');

        console.log("position ONE saved:" + positionONEsaved);

        if (positionONEsaved) {
          elONE.style.transform = positionONEsaved;
          elONE.xOffset = localStorage.getItem('positionONE_x');
          elONE.yOffset = localStorage.getItem('positionONE_y');
        }
        if (positionTWOsaved) {
          elTWO.style.transform = positionTWOsaved;
          elTWO.xOffset = localStorage.getItem('positionTWO_x');
          elTWO.yOffset = localStorage.getItem('positionTWO_y');
        }
        if (positionTHREEsaved) {
          elTHREE.style.transform = positionTHREEsaved;
          elTHREE.xOffset = localStorage.getItem('positionTHREE_x');
          elTHREE.yOffset = localStorage.getItem('positionTHREE_y');
        }

      };

      /////
      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;
        }

        //MY ADD Store Info to Local Storage
        setTimeout(storePositions, 111);

        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)";
        //el.style.top = xPos + "px";
        //el.style.left = yPos + "px";
      }

      //
      document.addEventListener("DOMContentLoaded", function (event) {
        setTimeout(retrievePositions, 0);//MY ADD

      });
    </script>
</body>

</html>

Notice that I am storing the xOffset and yOffset values for each of the three items. When retrieving the stored value, I read the stored xOffset and yOffset values and assign it directly to our element (elONE, elTWO, elTHREE). This ensures that our new dragging operation starts from the state originally set by the earlier drag.

:parasol_on_ground:

ā€¦Brilliant! - Many thanks indeed - itā€™s people like you who make this world a (slightly) better place.

1 Like

Hi @kirupa;
I try to develop my little project. It includes the multiple draggable Sortable container. I want to put another element inside the element. How can I do it?

Right now what we are dragging is a div element. You can just add more child elements into it, and that will now be draggable as well. Are you looking to drag another element into another element? At which point two separate elements become one combined element?

Hi @kirupa , it had helped me a lot for dragging an element. Am using the drag to have horizontal page scroll. My only concern now is, as there someone (@Cezary ) had already asked about the draggable element is going beyond itā€™s parent(container) element. As the parent has overflow: hidden the element looks like cropped in the border. Can you provide a solution to stop that inside the parent element, it will help me a lot.

Welcome to the forums @Manjunath_R :partying_face:

If I understood your question correctly, you want the draggable element to stop just before hitting the boundaries. In other words, it will never look cropped. Does that sound right? :slight_smile:

Exactly @kirupa!!. Glad to see your response so soon.

A post was split to a new topic: Dragging items between lists

I donā€™t have a solution ready to share, but this is something I will fiddle with over the next week or so. The main work revolves around first identifying the boundaries of the container, boundaries of the element being dragged, and checking for overlaps to block the drag when an overlap is detected.

There exist various vanilla drag and drop libraries with robust feature sets. So I wonder at what point regarding features does reinventing the wheel from scratch start to come into question? I appreciate the learning aspect, but when features start getting overly robust then sometimes efficient reusable libraries can become beneficial.

1 Like

@prg9 - That is actually a good point. Drag/drop functionality is something best done by a 3rd party library that handles all the edge cases. Do you have a good one that youā€™d recommend?

Here are a few that come to mind, while many others abound also.

Most of these are pretty vanilla yet very extensible further with user logic as desired. Some are similar while others have specific features which make them unique. I would say a few of these are quite relative to questions that have been asked regarding drag and drop from your tutorials. All of which would be a worthwhile review for consideration by users seeking drag and drop capabilities, some may prove useful to someone.

1 Like

Thanks! I will be revising this tutorial shortly, so I will revise some of the basic drag explanations (for science, of course :P) and then point to your list for drag/drop next steps.