I'm using interact.js and Svelte 5 (version 5.0.0-next.200) to drag an element which initially sits inside a scrollable div and must be dropped outside of that div. To make this work I need to clone the element and then drag the clone (as far as I know, there is no way around).
Now, in a former Vanilla-js project I successfully adapted the code from this stackoverflow-answer and everything worked fine. However, I can't make it work in Svelte.
Below is my code. I assume the error is in interaction.start({ name: 'drag' }, ev.interactable, clone);
, which fails to pass the drag-action on to the clone.
Any help is warmly welcome. I'm struggling with this since days.
<script>
import interact from 'interactjs';
const dragElement = (ev) => {
const el = ev.target;
let x = (parseFloat(el.getAttribute('data-x')) || 0) + ev.dx;
let y = (parseFloat(el.getAttribute('data-y')) || 0) + ev.dy;
el.style.webkitTransform = el.style.transform = `translate(${x}px,${y}px)`;
el.setAttribute('data-x', x);
el.setAttribute('data-y', y);
}
const makeDraggable = (DRAG) => {
const draggable = document.querySelector('.draggable');
interact(draggable)
.draggable({
allowFrom: '*',
inertia: false,
autoScroll: false,
modifiers: [
interact.modifiers.restrict({
restriction: '#outer',
endOnly: true
})
],
onstart: (ev) => {
console.log('DRAG START of ', ev.target);
},
onmove: (ev) => {
console.log('DRAG MOVE of ', ev.target);
dragElement(ev);
},
onend: (ev) => {
console.log('DRAG END of ', ev.target);
DRAG.node.orig.style.opacity = 100; // show original again after drag
}
})
.on('move', (ev) => {
const el = ev.currentTarget;
const interaction = ev.interaction;
console.log('ON.MOVE TRY of ', el);
if (
interaction.pointerIsDown && // Only on active interaction
interaction.interacting() // Prevent activation by swiping through element
) {
// If ORIG then make a clone
if (
DRAG.active == false && // Create only one clone
el.getAttribute('isClone') == 'orig' // Prevent re-cloning clones
) {
DRAG.active = true;
console.log('CLONING START');
// Clone node and set its position
let clone = el.cloneNode(true);
clone.style.left = el.offsetLeft + 'px';
clone.style.top = el.offsetTop + 'px';
clone.style.position = 'absolute';
clone.setAttribute('isClone', 'clone');
clone.style.backgroundColor = 'grey';
// Append Clone and start drag interaction
document.querySelector('#outer').appendChild(clone);
console.log('CLONE IS READY: ', clone);
DRAG.node.orig = el;
DRAG.node.clone = clone;
DRAG.node.orig.style.opacity = 0; // hide original while dragging clone
interaction.start({ name: 'drag' }, ev.interactable, clone);
// if CLONE then drag
// } else if (el.getAttribute('isClone') == 'clone') {
// // console.log('MOVING THE CLONE START');
// // dragElement(ev);
// }
}
}
});
};
///////////////////////////////////////////////////////////////
// DRAG State
let DRAG = $state({
active: false,
node: {
orig: undefined,
clone: undefined
}
});
///////////////////////////////////////////////////////////////
// Handle drag
const handleDrag = () => {
makeDraggable(DRAG);
};
</script>
<div id="outer">
<h1>outer</h1>
<div id="inner">
<h1>inner (scrollable)</h1>
<div class="draggable" isClone="orig" use:handleDrag>Drag me</div>
</div>
</div>
<style>
#outer {
position: absolute;
background-color: yellow;
height: 95vh;
width: 90vw;
}
#inner {
position: absolute;
height: 400px;
width: 100%;
background: lightblue;
overflow-y: scroll;
}
.draggable {
position: absolute;
background-color: black;
color: white;
height: 500px;
width: 150px;
}
</style>
Adding interaction.stop();
before interaction.start(...);
allows you to move the clone.
Note that Svelte 5 is sensitive to DOM structure manipulations - it is already incompatible with some drag-n-drop libs.