@svelte-put/movable

GitHub Github

move node on mousedown

@svelte-put/movable @svelte-put/movable @svelte-put/movable @svelte-put/movable @svelte-put/movable

Still on Svelte 4? See the old docs site here.

Installation

npm install --save-dev @svelte-put/movable
pnpm add -D @svelte-put/movable
yarn add -D @svelte-put/movable

New to Svelte 5? See Migration Guides.

Quick Start

In the following minimal example, try dragging the blue box around.

Example

Box below can be moved around

...
<script lang="ts">
	import { movable } from '@svelte-put/movable';
</script>

<div class="hl-info z-overlay grid h-20 w-20 place-items-center" use:movable>...</div>

Events

<div use:movable onmovablestart onmovableend />

movable provides two CustomEvents, movablestart and movableend, with event.detail as MovableEventDetail.

export interface MovableEventDetail {
	/** the node that the action was placed on */
	node: HTMLElement;
	/** last known position, as in styles.position */
	position: {
		/** styles.position.left */
		left: number;
		/** styles.position.right */
		top: number;
	};
}

Setting Limit

By default, as seen in Quick Start, node that uses movable can be moved freely. The limit option can be set to limit the "movable" zone.

Limit within Screen

<div use:movable={{ limit: { parent: 'screen' } }} />

Limit to the viewport by setting limit.parent to 'screen'.

Example

Box below can be moved around, but only within viewport

...
<script>
	import { movable } from '@svelte-put/movable';
</script>

<div
	class="hl-info z-overlay grid h-20 w-20 place-items-center"
	use:movable={{ limit: { parent: 'screen' } }}
>
	...
</div>

Limit within an HTMLElement Ancestor

<div use:movable={{ limit: { parent: ancestorNode } }} />

Limit to an ancestor by referencing limit.parent to that ancestor.

Box below can be moved around, but only within the violet border

...
<script lang="ts">
	import { movable } from '@svelte-put/movable';

	let parentNode: HTMLElement;
</script>

<div class="grid place-items-center border-2 border-violet-500 p-4" bind:this={parentNode}>
	<p class="text-center">Box below can be moved around, but only within the violet border</p>
	<div
		class="hl-info grid h-20 w-20 place-items-center"
		use:movable={{ limit: { parent: parentNode } }}
	>
		...
	</div>
</div>

movable cannot take into account scrollbars. So, for example, if your limit.parent is a scroll container with visible vertical scrollbar, moving the "movable" node to the scrollbar region will cause overflow in the horizontal direction, triggering the horizontal scrollbar to appear.

Limit within Delta

<div use:movable={{ limit: { delta: '50%' } }} />
<div use:movable={{ limit: { delta: '250px' } }} />
<div use:movable={{ limit: { delta: { x: '20%', y: '100px' } } }} />

Set limit.delta to limit movable to a certain travel distance. It takes a number with unit of % or px in one or both axes, and can be set insolation or in combination with limit.parent.

Example

Box below can be moved around, but only within a delta of ±100% (of its own size).

...
<script>
	import { movable } from '@svelte-put/movable';
</script>

<div class="hl-info grid h-20 w-20 place-items-center" use:movable={{ limit: { delta: '100%' } }}>
	...
</div>

Limit to Single Axis

<div use:movable={{ limit: { delta: { x: 0 } } }} />
<div use:movable={{ limit: { delta: { y: 0 } } }} />

Setting limit.delta.x or limit.delta.y to 0 effectively disables movement in that axis.

Example

Box below can be moved around only in the x axis, combined with a delta limit of ±100% (of its width).

...
<script>
	import { movable } from '@svelte-put/movable';
</script>

<div
	class="hl-info grid h-20 w-20 place-items-center"
	use:movable={{ limit: { delta: { x: '100%', y: 0 } } }}
>
	...
</div>

Custom Handle

Handle is an HTMLElement on which movable will listen for pointer events to initiate and map movement to the target node.

<div use:movable={{ handle: someNode }} />

By default, the target node itself is the handle. To use a custom handle, set handle to the desired HTMLElement.

Example

Move blue box by dragging the yellow box

.
...
<script lang="ts">
	import { movable } from '@svelte-put/movable';

	let handle: HTMLElement | undefined = $state();
</script>

<div class="hl-info flex h-40 w-40 flex-col p-2" use:movable={{ handle }}>
	<div class="hl-warning grid h-8 w-8 place-items-center self-end" bind:this={handle}>.</div>
	<div class="grid flex-1 place-items-center self-stretch">...</div>
</div>

Ignore Children Nodes from Triggering movable on Click

Prefer handle in favor of ignore when possible for better predictability.

The ignore option takes one or more CSS selector strings and will prevent matching children of handle from triggering movable.

Example

mousedown on red box will not trigger movable

<script>
	import { movable } from '@svelte-put/movable';
	import { onMount } from 'svelte';

	let mounted = false;
	onMount(() => {
		setTimeout(() => {
			mounted = true;
		}, 500);
	});
</script>

<div
	class="hl-info z-overlay grid h-40 w-40 place-items-center"
	use:movable={{ ignore: '.to-ignore' }}
>
	{#if mounted}
		<p class="to-ignore hl-error cursor-auto p-2 text-sm">To ignore</p>
	{/if}
</div>

Disabling Cursor Handling

<div use:movable={{ cursor: false }} />

By default, movable adds cursor: grab to handle, and cursor: grabbing on mousedown to both handle and window.body. This can be disabled by setting the cursor option to false.

Automatic cursor handling is done on action initialization and update. It won't work for ignored children that are dynamically added to DOM at runtime. In such cases, disable cursor or add cursor styles manually to the ignored children.

Migration Guides

V3 -> V4 (Svelte 5 in Runes mode)

When migrating from v3 to v4, you just need to update event attribute syntax to remove colon ::

<div use:movable on:movablestart on:movableend></div>
<div use:movable onmovablestart onmovableend></div>

Happy moving node! 👨‍💻

Edit this page on Github