How to snap a draggable HTML element to original position when detected by a dropzone?
lilrichan opened this issue · comments
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')
}
})
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/