I am trying to refactor a dragListener into a .svelte.js
module by defining the position of the dragged element as a reactive $state()
variable inside +page.svelte
and pass it back and forth between the module and the +page.svelte
.
The Svelte 5 Docs provide an example where let position = $state()
is defined inside the external module (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>Position: {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;}
Note: 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); 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>Position: {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>