Search code examples
javascripthtmljquerycssinteractjs

Can't drag and drop to another div using interact.js


Html/css newbie here,

I am using interact.js to attain a certain UI where I can drop items from a list into a drop zone. The problem is my list can grow very long and I need to add scroll to the list, and when I try to do so I have create a 'div' and put the list items into it to show the scroll behaviour.

But when I put the list items inside a div and the drop zone outside it, the items do not get dropped there and the list shows a weird behaviour, all of them starts getting stacked on top of the bottom list item (at the bottom of the div containing the items).

This is my html:

<div class="items">
    <p style="padding-top: 20px; font-size:18px">Stacked Items</p>
    <div style = "height: 400px; overflow: scroll">
      <div id="itemstodrop" class="drag-drop"> item 1 </div>
      <div id="itemstodrop" class="drag-drop"> item 2 </div>
      <div id="itemstodrop" class="drag-drop"> item 3 </div>
      <div id="itemstodrop" class="drag-drop"> item 4 </div>
      <div id="itemstodrop" class="drag-drop"> item 5 </div>
      <div id="itemstodrop" class="drag-drop"> item 6 </div>
      <div id="itemstodrop" class="drag-drop"> item 7 </div>
      <div id="itemstodrop" class="drag-drop"> item 8 </div>
      <div id="itemstodrop" class="drag-drop"> item 9 </div>
      <div id="itemstodrop" class="drag-drop"> item 10 </div>
      <div id="itemstodrop" class="drag-drop"> item 11 </div>
      <div id="itemstodrop" class="drag-drop"> item 12 </div>
    </div>
    <div id="inner-dropzone" class="dropzone">Drop here</div>
</div>

This is my script which holds interact.js code:

import interact from 
'https://cdn.jsdelivr.net/npm/@interactjs/interactjs/index.js'
// enable draggables to be dropped into this

var startPos = null;
//define the initial position for each rig tile.
function dragMoveListener (event) {

  var target = event.target
  // keep the dragged position in the data-x/data-y attributes
  var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
  var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy

  // translate the element
  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)
}

// this function is used later in the resizing and gesture demos
window.dragMoveListener = dragMoveListener


//ALL THE FUNCTIONS BELOW TALK ABOUT THE DRAGGABLE AREA.
interact('.dropzone').dropzone({
  // only accept elements matching this CSS selector
  accept: '#itemstodrop',
  // the item should go 100% inside the draggable area...
  overlap: 0.9999999, //somehow it does not take the value 100%

  // listen for drop related events:

  ondropactivate: function (event) {
    // add active dropzone feedback
    event.target.classList.add('drop-active')
  },
  ondragenter: function (event) {

    //when item enters the draggable area
    var draggableElement = event.relatedTarget
    var dropzoneElement = event.target

    console.log(draggableElement.firstChild.nodeValue);
    // feedback the possibility of a drop
    dropzoneElement.classList.add('drop-target')
    draggableElement.classList.add('can-drop')

  },
  ondragleave: function (event) {


    console.log(startPos)
    event.draggable.draggable({
        snap: {
        targets: [startPos]
        }
    });

    //when item leaves the draggable area
    // remove the drop feedback style
    event.target.classList.remove('drop-target')
    event.relatedTarget.classList.remove('can-drop')

  },
  ondrop: function (event) {

    //when the item is droppd in the draggable area
    {% comment %} event.relatedTarget.textContent = 'Dropped' {% endcomment %}
  },
  ondropdeactivate: function (event) {
    // remove active dropzone feedback
    event.target.classList.remove('drop-active')
    event.target.classList.remove('drop-target')
  }
})

interact('.drag-drop')
  .draggable({
    inertia: true,
    modifiers: [
      interact.modifiers.restrictRect({
        restriction: 'parent',
        endOnly: true
      })
    ],
    autoScroll: true,
    // dragMoveListener from the dragging demo above
    listeners: { move: dragMoveListener }
  })
</script>

This is how it looks when rendered: (Basically can't drop any item to drop area.)

enter image description here

Full working page for demo: (I have added a comment which tells where have I put the div.)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
#outer-dropzone {
  height: 140px;
}

#inner-dropzone {
  height: 80px;
}

.dropzone {
  background-color: #ccc;
  border: dashed 4px transparent;
  border-radius: 4px;
  margin: 10px auto 30px;
  padding: 10px;
  width: 80%;
  transition: background-color 0.3s;
}

.drop-active {
  border-color: #aaa;
}

.drop-target {
  background-color: #29e;
  border-color: #fff;
  border-style: solid;
}

.drag-drop {
  display: inline-block;
  min-width: 40px;
  padding: 2em 0.5em;

  color: #fff;
  background-color: #29e;
  border: solid 2px #fff;

  touch-action: none;
  -webkit-transform: translate(0px, 0px);
          transform: translate(0px, 0px);

  transition: background-color 0.3s;
}

.drag-drop.can-drop {
  color: #000;
  background-color: #4e4;
}
</style>
</head>
<body>
{% comment %} I NEED THIS DIV UNDER THIS COMMENT, TO ADD A SCROLL AREA. BUT WHEN I GIVE THIS DIV, THE DROP DOES  NOT OCCUR. {% endcomment %}
  <div> 
    <div id="no-drop" class="drag-drop"> #no-drop </div>
    <div id="yes-drop" class="drag-drop"> #yes-drop </div>
  </div>

  <div id="outer-dropzone" class="dropzone">
  #outer-dropzone
  <div id="inner-dropzone" class="dropzone">#inner-dropzone</div>
 </div>



<script type="module">
import interact from 
'https://cdn.jsdelivr.net/npm/@interactjs/interactjs/index.js'
// enable draggables to be dropped into this


interact('.draggable')
  .draggable({
    // enable inertial throwing
    inertia: true,
    // keep the element within the area of it's parent
    modifiers: [
      interact.modifiers.restrictRect({
        restriction: 'parent',
        endOnly: true
      })
    ],
    // enable autoScroll
    autoScroll: true,

    listeners: {
      // call this function on every dragmove event
      move: dragMoveListener,

      // call this function on every dragend event
      end (event) {
        var textEl = event.target.querySelector('p')

        textEl && (textEl.textContent =
          'moved a distance of ' +
          (Math.sqrt(Math.pow(event.pageX - event.x0, 2) +
                     Math.pow(event.pageY - event.y0, 2) | 0))
            .toFixed(2) + 'px')
      }
    }
  })

function dragMoveListener (event) {
  var target = event.target
  // keep the dragged position in the data-x/data-y attributes
  var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
  var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy

  // translate the element
  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)
}

// this function is used later in the resizing and gesture demos
window.dragMoveListener = dragMoveListener


//ALL THE FUNCTIONS BELOW TALK ABOUT THE DRAGGABLE AREA.
interact('.dropzone').dropzone({
  // only accept elements matching this CSS selector
  accept: '#yes-drop',
  // the item should go 100% inside the draggable area...
  overlap: 0.9999999, //somehow it does not take the value 100%

  // listen for drop related events:

  ondropactivate: function (event) {
    // add active dropzone feedback
    event.target.classList.add('drop-active')
  },
  ondragenter: function (event) {

    //when item enters the draggable area
    var draggableElement = event.relatedTarget
    var dropzoneElement = event.target

    console.log(draggableElement.firstChild.nodeValue);
    // feedback the possibility of a drop
    dropzoneElement.classList.add('drop-target')
    draggableElement.classList.add('can-drop')

  },
  ondragleave: function (event) {




    //when item leaves the draggable area
    // remove the drop feedback style
    event.target.classList.remove('drop-target')
    event.relatedTarget.classList.remove('can-drop')

  },
  ondrop: function (event) {

    //when the item is droppd in the draggable area
    {% comment %} event.relatedTarget.textContent = 'Dropped' {% endcomment %}
  },
  ondropdeactivate: function (event) {
    // remove active dropzone feedback
    event.target.classList.remove('drop-active')
    event.target.classList.remove('drop-target')
  }
})

interact('.drag-drop')
  .draggable({
    inertia: true,
    modifiers: [
      interact.modifiers.restrictRect({
        restriction: 'parent',
        endOnly: true
      })
    ],
    autoScroll: true,
    // dragMoveListener from the dragging demo above
    listeners: { move: dragMoveListener }
  })
</script>
</body>
</html>

Solution

  • You need to change accept: '#rigstodrop' to accept: '#itemstodrop' because here your div has id itemstodrop which you need to drag and drop . Also , use restriction: '.items' to restrict rect not to go beyond that area .

    Demo code :

    var startPos = null;
    //define the initial position for each rig tile.
    function dragMoveListener(event) {
    
      var target = event.target
      // keep the dragged position in the data-x/data-y attributes
      var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
      var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
    
      // translate the element
      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)
    }
    
    // this function is used later in the resizing and gesture demos
    window.dragMoveListener = dragMoveListener
    interact('.dropzone').dropzone({
      // only accept elements matching this CSS selector
      accept: '#itemstodrop', //change this
      // the item should go 100% inside the draggable area...
      overlap: 0.9999999,
      ondropactivate: function(event) {
        // add active dropzone feedback
        event.target.classList.add('drop-active')
      },
      ondragenter: function(event) {
    
        //when item enters the draggable area
        var draggableElement = event.relatedTarget
        var dropzoneElement = event.target
    
        console.log(draggableElement.firstChild.nodeValue);
        // feedback the possibility of a drop
        dropzoneElement.classList.add('drop-target')
        draggableElement.classList.add('can-drop')
    
      },
      ondragleave: function(event) {
    
    
        console.log(startPos)
        event.draggable.draggable({
          snap: {
            targets: [startPos]
          }
        });
    
        //when item leaves the draggable area
        // remove the drop feedback style
        event.target.classList.remove('drop-target')
        event.relatedTarget.classList.remove('can-drop')
    
      },
      ondrop: function(event) {
    
        //when the item is droppd in the draggable area
        event.relatedTarget.textContent = 'Dropped'
      },
      ondropdeactivate: function(event) {
        // remove active dropzone feedback
        event.target.classList.remove('drop-active')
        event.target.classList.remove('drop-target')
      }
    })
    
    interact('.drag-drop')
      .draggable({
        inertia: true,
        modifiers: [
          interact.modifiers.restrictRect({
            restriction: '.items', //change this
            endOnly: true
          })
        ],
        autoScroll: true,
        // dragMoveListener from the dragging demo above
        listeners: {
          move: dragMoveListener
        }
      })
    .itemstodrop {
      touch-action: none;
      user-select: none;
    }
    
    
    #inner-dropzone {
      height: 80px;
    }
    
    .dropzone {
      background-color: #ccc;
      border: dashed 4px transparent;
      border-radius: 4px;
      margin: 10px auto 30px;
      padding: 10px;
      width: 80%;
      transition: background-color 0.3s;
    }
    
    .drop-active {
      border-color: #aaa;
    }
    
    .drop-target {
      background-color: #29e;
      border-color: #fff;
      border-style: solid;
    }
    
    .drag-drop {
      display: inline-block;
      min-width: 40px;
      padding: 2em 0.5em;
      color: #fff;
      background-color: #29e;
      border: solid 2px #fff;
      touch-action: none;
      -webkit-transform: translate(0px, 0px);
      transform: translate(0px, 0px);
      transition: background-color 0.3s;
    }
    
    .drag-drop.can-drop {
      color: #000;
      background-color: #4e4;
    }
    <script src="https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js"></script>
    <div class="items">
      <p style="padding-top: 20px; font-size:18px">Stacked Rigs</p>
      <div style="height: auto;overflow: scroll">
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 1 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 2 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 3 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 4 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 5 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 6 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 7 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 8 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 9 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 10 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 11 </div>
        <div id="itemstodrop" class="drag-drop itemstodrop"> item 12 </div>
        <!--put this inside div-->
        <div id="inner-dropzone" class="dropzone">Drop here</div>
      </div>
    
    
    
    
    </div>