javascriptdraggabletouch-eventinteractjs

How to make draggable element follow cursor on touchmove coords with InteractJs


I have one draggable element inside container and restricted to its bounds. I use InteractJs to implement this. Draggable element is hidden by default and becomes visible as touch starts.

However I want to make the draggable element to follow the cursor/touch position of the user so that it can appear right under the position of the first users click/touch and then follow it while drag/touchmove and dissapear on touchend.

Could you please advise how can I modify my code to reach such behavior? I've tried to apply the transform style property with the coordinates of touchstart but no luck.

Example: https://codepen.io/moogeek/pen/oNQWwaN

<!DOCTYPE html>
<html>
   <head>
      <title></title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.17/interact.min.js"></script>
      <style>
         body{padding-top:100px;}
         .container {
         width:368px;
         height:120px;
         background-color:red;
         margin:0 auto;
         z-index:2
         }
         .draggable {
         visibility:hidden;
         height:60px;
         width:80px;
         background-color:#fff;
         display:inline-block;
         touch-action:none;
         z-index:1;
         }
      </style>
   </head>
   <body>
      <div class="container">
         <div class="draggable"></div>
      </div>
   </body>
</html>

js:

let dragMoveListener=(event) => {
 let draggable=document.getElementsByClassName("draggable")[0],
     target = event.target,
  // keep the dragged position in the data-x/data-y attributes
  x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
  y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

  draggable.style.visibility='visible';
  target.style.backgroundPosition=x + 'px ' + y + 'px';
  target.style.webkitTransform = target.style.transform
                               = 'translate(' + x + 'px, ' + y + 'px)';
  // update the posiion attributes
  target.setAttribute('data-x', x);
  target.setAttribute('data-y', y);
}

let draggables=document.querySelectorAll(".draggable"),
    containers=document.querySelectorAll(".container");

    containers.forEach(function(element){
        interact(element).on("tap down",function(e){
            var wt=element.querySelector('.draggable');
            var wect=wt.getBoundingClientRect();
            wt.style.visibility='visible';

       });
    });

    draggables.forEach(function(element){
        interact(element)
        .on("dragstart",function(e){
            var x=element.getAttribute("data-x");
            var y=element.getAttribute("data-y");
            if(x!=null && y!=null){
                //draggable.webkitTransform = draggable.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
            }
        }).
        on("dragmove",function(e){
        })
        .on("dragend",function(e){
            element.style.visibility='hidden';
            //draggable.webkitTransform = draggable.style.transform = 'translate(0px, 0px)';
        }).draggable({
        onmove: dragMoveListener,
        modifiers: [
                interact.modifiers.restrict({
                restriction: 'parent',
                endOnly: false
                })
        ]})
    });

Solution

  • To achieve this you can try this.

    1. Add a new element with the class "follower" inside your container element. This element will act as the follower element.
    2. On container tap/down, set the position of the follower element to the position of the user's click/touch and make it visible.
    3. On container dragmove, update the position of the follower element so that the draggable element can follow it while dragging.
    4. On container dragend, hide the follower element and reset its position.

    See the demo below

        let dragMoveListener = (event) => {
          let draggable = document.querySelector(".draggable"),
              follower = document.querySelector(".follower"),
              target = event.target,
              x = event.clientX,
              y = event.clientY;
    
          follower.style.visibility = 'visible';
          follower.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
    
          target.style.backgroundPosition = x + 'px ' + y + 'px';
        };
    
        let containers = document.querySelectorAll(".container");
    
        containers.forEach(function (element) {
          interact(element).on("tap down", function (e) {
            var wt = element.querySelector('.draggable');
            var wect = wt.getBoundingClientRect();
            var follower = element.querySelector('.follower');
    
            // Set the position of the follower element to the position of the user's click/touch
            var x = e.clientX - wect.left;
            var y = e.clientY - wect.top;
            follower.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
            follower.style.visibility = 'visible';
          });
        });
    
        containers.forEach(function (element) {
          interact(element)
            .on("dragstart", function (e) {
              var x = element.getAttribute("data-x");
              var y = element.getAttribute("data-y");
              if (x != null && y != null) {
                //draggable.webkitTransform = draggable.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
              }
            })
            .on("dragmove", dragMoveListener)
            .on("dragend", function (e) {
              var draggable = document.querySelector(".draggable");
              var follower = document.querySelector(".follower");
              draggable.style.visibility = 'hidden';
              follower.style.visibility = 'hidden';
              //draggable.webkitTransform = draggable.style.transform = 'translate(0px, 0px)';
            })
            .draggable({
              onmove: dragMoveListener,
              modifiers: [
                interact.modifiers.restrict({
                  restriction: 'parent',
                  endOnly: false
                })
              ]
            });
        });
      
        .container {
          width: 300px;
          height: 300px;
          position: relative;
          background-color: red;
        }
    
        .draggable {
          width: 100px;
          height: 100px;
          background-color: #fff;
          color: white;
          text-align: center;
          line-height: 100px;
          user-select: none;
          cursor: move;
          position: absolute;
          top: 0;
          left: 0;
          visibility: hidden;
        }
    
        .follower {
          width: 100px;
          height: 100px;
          background-color: #fff;
          opacity: 1;
          position: absolute;
          top: 0;
          left: 0;
          visibility: hidden;
        }
    <!DOCTYPE html>
    <html>
    <head>
      <title>Draggable Element</title>
    </head>
    <body>
      <div class="container">
        <div class="draggable">Draggable Element</div>
        <div class="follower"></div>
      </div>
    
      <script src="https://unpkg.com/interactjs"></script>  
    </body>
    </html>

    JSBIN DEMO