I am trying to refactor a dragListener into a .svelte.js
module passing the position of the dragged element as a reactive $state().
The Svelte 5 Docs provide an example where let position = $state()
is defined inside the external module (by returning a getter function). But what shall I do, if I need to define let position = $state()
inside +page.svelte
?
Below I provide a minimal working example where the position of the element can be changed via a button and via drag. For both I provide handles in a helpers.svelte.js
file. Inside +page.svelte
I use $inspect(position)
to check, whether state has updated. While inspect will fire on button click, it does not on drag.
I suppose, however, that the button-event only appears to be reactive because position
is returned by handleClick()
. While the lack of true reactivity is ok for a simple click action, it is an issue in the case of the drag listener.
So, how can I make position truly reactive?
Btw: I would be fine with using stores, too. However I don’t get them working in external svelte.js
files either. Also, according to the above linked Svelte 5 Docs, there should be a way to solve this with the $state()
rune.
+page.svelte
<svelte:options runes="{true}" />
<script>
import { onMount } from "svelte";
import {handleIncreaseX, handleDrag} from '$lib/helpers.svelte'
let item;
let position = $state({x:0, y:0});
$inspect(position); //??? why is it only reacting to the button but not to drag?
let onclick = () => {
position = handleIncreaseX(item, position)
}
onMount(() => {
position = handleDrag(item, position);
});
</script>
<h1>Testing state: {position.x} / {position.y}</h1>
<button {onclick}>Increase X</button>
<div id="draggable" bind:this={item}>Drag Me</div>
<style>
#draggable {
background-color: black;
color: white;
width: 200px;
}
</style>
helpers.svelte.js
import interact from 'interactjs';
export function handleIncreaseX(item, position) {
position.x += 1;
item.style.webkitTransform = item.style.transform = `translate(${position.x}px,${position.y}px)`;
item.setAttribute('data-x', position.x);
item.setAttribute('data-y', position.y);
return position;
}
export function handleDrag(item, position) {
console.log('DRAG INIT')
interact(item).draggable({
inertia: false,
autoScroll: false,
onstart: (ev) => {
console.log('ONSTART');
},
onmove: (ev) => {
let el = ev.target;
// store the dragged position inside data-x/data-y attributes
let x = (parseFloat(el.getAttribute('data-x')) || 0) + ev.dx;
let y = (parseFloat(el.getAttribute('data-y')) || 0) + ev.dy;
// translate the element and update position attributes
el.style.webkitTransform = el.style.transform = `translate(${x}px,${y}px)`;
el.setAttribute('data-x', x);
el.setAttribute('data-y', y);
position = {x:x, y:y};
console.log('ONMOVE', position);
},
onend: (ev)=>{
console.log('ONEND');
}
});
return position;
}
Comment 1: Reactivity does work perfectly fine, as long as I define the drag listener inside the +page.svelte
. However, this is not an option for me.
+page.svelte
<svelte:options runes="{true}" />
<script>
import { onMount } from "svelte";
import interact from 'interactjs';
import {handleIncreaseX} from '$lib/helpers.svelte'
let item;
let position = $state({x:0, y:0});
$inspect(position); //??? why is it only reacting to the button but not to drag?
let onclick = () => {
position = handleIncreaseX(item, position)
}
onMount(() => {
console.log('DRAG INIT')
interact(item).draggable({
inertia: false,
autoScroll: false,
onstart: (ev) => {
console.log('ONSTART');
},
onmove: (ev) => {
let el = ev.target;
// store the dragged position inside data-x/data-y attributes
let x = (parseFloat(el.getAttribute('data-x')) || 0) + ev.dx;
let y = (parseFloat(el.getAttribute('data-y')) || 0) + ev.dy;
// translate the element and update position attributes
el.style.webkitTransform = el.style.transform = `translate(${x}px,${y}px)`;
el.setAttribute('data-x', x);
el.setAttribute('data-y', y);
position = {x:x, y:y};
console.log('ONMOVE', position);
},
onend: (ev)=>{
console.log('ONEND');
}
});
});
</script>
<h1>Testing state: {position.x} / {position.y}</h1>
<button {onclick}>Increase X</button>
<div id="draggable" bind:this={item}>Drag Me</div>
<style>
#draggable {
background-color: black;
color: white;
width: 200px;
}
</style>
The_Lab is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.