taye / interact.js

JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+)
http://interactjs.io/
MIT License
12.38k stars 785 forks source link

How to snap a draggable HTML element to original position when detected by a dropzone? #1001

Open lilrichan opened 1 year ago

lilrichan commented 1 year ago

I'm sorry if this sounds troublesome as I feel this question has been asked many times or this could possibly be a duplicate.

I'm actually working on a click and drag bunny rabbit dissection game and what's supposed to happen is when I place one of the divs within the transparent dropzone, it should snap back into it's original position.

There is only one dropzone that spans all the way from the top of the rabbit's head (including the little hair) all the way down to the bottom of the torso. All draggable elements are positioned relatively using left and bottom.

The issue is, I've tried every method I found on here and none of them have worked to my expectations, I have a feeling it's due to the way my website is structured as it depends on HTML elements rather than a JavaScript canvas.

The game is here, feel free to mess around in Inspect Element=> https://yumekawahanachi.neocities.org/dissectbunny Be warned as there are elements of light gore, no jumpscares or screamers though.

Methods I've tried => results:

79 => Nothing Happens

265 => Snaps to a random corner of the HTML document

265 combined with #79 => Snaps to a random corner of the HTML document

I'm still very new to JavaScript as a whole and I'm coming here from HTML and CSS, which is why my game is based on HTML elements.

My code as of writing this looks like this:

  const organs = new Audio('https://dl.dropbox.com/s/ja6r3s11xj5gpnu/organs.mp3');
    organs.volume = 0.8;
    const cloth = new Audio('https://dl.dropbox.com/s/mz7ilj4hvdew8lp/cloth.mp3');
    cloth.volume = 0.5;
    const tray = new Audio('https://dl.dropbox.com/s/4pvsnxkgwu0o4mm/tray.mp3');
    tray.volume = 0.4;
    const rip = new Audio('https://dl.dropbox.com/s/b30vggiyioqsuh0/rip.mp3'); 

var startPos = {x: 0, y: 0};

function dragMoveListener (event) {
    var 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;

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

function dragStartListener (event) {
    var rect = interact.getElementRect(event.target);

    // record center point when starting a drag
    startPos.x = rect.left + rect.width  / 2;
    startPos.y = rect.top  + rect.height / 2;
    event.interactable.snap({ anchors: [startPos] });
}
// target elements with the "draggable" class
interact('.draggable')
  .draggable({
    // enable inertial throwing
    inertia: true,
    // keep the element within the area of it's parent
    modifiers: [
      interact.modifiers.restrictRect({
        restriction: document.getElementById('tray'),
        endOnly: true
      })
    ],

    // enable autoScroll
    autoScroll: true,

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

      onstart: dragStartListener,

      // call this function on every dragend event
      end (event) {
        tray.load();
        tray.play();
      }
     }
  });

let outers = document.querySelectorAll('.outer')

let fluffs = document.querySelectorAll('.fluff')

let innards = document.querySelectorAll('.innard')

innards.forEach( innard => {
   innard.addEventListener("mousedown",()=>{organs.load();organs.play(); innard.style.zIndex = '200'});
   innard.addEventListener("touchstart",()=>{organs.load();organs.play(); innard.style.zIndex = '200'});
   innard.addEventListener("mouseup",()=>{organs.play(); innard.style.zIndex = '10'});
   innard.addEventListener("touchend",()=>{organs.play(); innard.style.zIndex = '10'});
})

fluffs.forEach( fluff => {
   fluff.addEventListener("mousedown",()=>{cloth.load();cloth.play(); fluff.style.zIndex = '200'});
   fluff.addEventListener("touchstart",()=>{cloth.load();cloth.play(); fluff.style.zIndex = '200'});
   fluff.addEventListener("mouseup",()=>{cloth.play(); fluff.style.zIndex = '10'});
   fluff.addEventListener("touchend",()=>{cloth.play(); fluff.style.zIndex = '10'});
})

outers.forEach( outer => {
   outer.addEventListener("mousedown",()=>{rip.load();rip.play(); outer.style.zIndex = '200'});
   outer.addEventListener("touchstart",()=>{rip.load();rip.play(); outer.style.zIndex = '200'});
   outer.addEventListener("mouseup",()=>{cloth.play(); outer.style.zIndex = '10'});
   outer.addEventListener("touchend",()=>{cloth.play(); outer.style.zIndex = '10'});
});

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

/* The dragging code for '.draggable' from the demo above
 * applies to this demo as well so it doesn't have to be repeated. */
// enable draggables to be dropped into this
interact('.dropzone').dropzone({
  // only accept elements matching this CSS selector
  accept: '.outer, .innard, .fluff',
  // Require a 75% element overlap for a drop to be possible
  overlap: 0.75,

  // listen for drop related events:
  ondropactivate: function (event) {
    // add active dropzone feedback
    event.target.classList.add('drop-active')
  },
  ondragenter: function (event) {
    var draggableElement = event.relatedTarget
    var dropzoneElement = event.target

   // feedback the possibility of a drop
    dropzoneElement.classList.add('drop-target')
    draggableElement.classList.add('can-drop')
  },
  ondragleave: function (event) {
    var startPos = {x: 0, y: 0};
    // remove the drop feedback style
    event.target.classList.remove('drop-target');
    event.relatedTarget.classList.remove('can-drop');
    event.draggable.snap({ anchors: [startPos] });
  },
  ondrop: function (event) {
  },
  ondropdeactivate: function (event) {
    // remove active dropzone feedback
    event.target.classList.remove('drop-active')
    event.target.classList.remove('drop-target')
  }
})
turhtkar commented 1 year ago

it's a nice little short around, but what I did and what you could do is take the position of your mouse to calculate where the dropped element should be snapped to, so after dropping it, get it inside a container which will be your interactable object, and then add a transform translate to the container, with the values you calculated earlier for the 'real' position on screen for you object to be 'snapped' to, you can also use that method to just snap it to it, by saying 'real' position I mean the position on screen that you're mouse at but relative to the element where you drop or contain all of your interactable elements at.

to do that calculation, you will check your interactable elements container offsets or the left and top attributes with parentContainer.getBoundingClientRect(), and you will do droppedElementX = event.dx - parentContainer.getBoundingClientRect().left droppedElementY = event.dy - parentContainer.getBoundingClientRect().top which will give you the positions you want to set your dropped elements too, I'm not sure about snapping cause I've just started dealing with it myself, but I think you could then set the modifier on drop interact.modifiers.snap({ targets: [{droppedElementX, droppedElementY}], relativePoints: [{ x: 0.5, y: 0.5 }] })

and like it said in the docs -

interact(element).draggable({ modifiers: [ interact.modifiers.snap({ targets: [ { x: 300, y: 300 } ], relativePoints: [ { x: 0 , y: 0 }, // snap relative to the element's top-left, { x: 0.5, y: 0.5 }, // to the center { x: 1 , y: 1 } // and to the bottom-right ] }) ] })

so setting the relativePoints to { x: 0.5, y: 0.5 } should position it from the center. https://interactjs.io/docs/snapping/