bounds
Keep draggable elements within specified boundaries
The bounds
plugin keeps draggable elements within specified boundaries. Whether it’s staying inside a parent container, the viewport, or custom regions - bounds ensures your elements can’t escape their designated areas.
bounds(BoundsFrom.viewport()); // Stay in browser window
bounds(BoundsFrom.parent()); // Stay in parent element
bounds(BoundsFrom.element(el)); // Stay in specific element
Basic Usage
import { createSignal } from 'solid-js';
import { bounds, BoundsFrom, useDraggable } from '@neodrag/solid';
function BoundedElements() {
const [viewport, setViewport] = createSignal<HTMLElement | null>(
null,
);
const [parent, setParent] = createSignal<HTMLElement | null>(null);
useDraggable(viewport, [bounds(BoundsFrom.viewport())]);
useDraggable(parent, [bounds(BoundsFrom.parent())]);
return (
<div>
<div ref={setViewport}>Can't leave the screen</div>
<div class="container">
<div ref={setParent}>Contained child</div>
</div>
</div>
);
}
BoundsFrom Utilities
Viewport Boundaries
import { createSignal } from 'solid-js';
import { bounds, BoundsFrom, useDraggable } from '@neodrag/solid';
function ViewportBounds() {
const [basic, setBasic] = createSignal<HTMLElement | null>(null);
const [padded, setPadded] = createSignal<HTMLElement | null>(null);
useDraggable(basic, [bounds(BoundsFrom.viewport())]);
useDraggable(padded, [
bounds(
BoundsFrom.viewport({
top: 20,
left: 20,
right: 20,
bottom: 20,
}),
),
]);
return (
<div>
<div ref={setBasic}>Stay in window</div>
<div ref={setPadded}>20px padding from edges</div>
</div>
);
}
Element-Specific Boundaries
import { createSignal } from 'solid-js';
import { bounds, BoundsFrom, useDraggable } from '@neodrag/solid';
function ElementBounds() {
const [container, setContainer] = createSignal<HTMLElement | null>(
null,
);
const [bounded, setBounded] = createSignal<HTMLElement | null>(
null,
);
const [selector, setSelector] = createSignal<HTMLElement | null>(
null,
);
useDraggable(bounded, [
bounds(() => BoundsFrom.element(container()!)),
]);
useDraggable(selector, [bounds(BoundsFrom.selector('#drop-zone'))]);
return (
<div>
<div ref={setContainer} class="boundary-container">
<div ref={setBounded}>Bound to container</div>
</div>
<div class="zone" id="drop-zone">
<div ref={setSelector}>Bound by selector</div>
</div>
</div>
);
}
Custom Boundary Functions
import { createSignal } from 'solid-js';
import { bounds, useDraggable } from '@neodrag/solid';
function CustomBounds() {
const [circular, setCircular] = createSignal<HTMLElement | null>(
null,
);
const [time, setTime] = createSignal<HTMLElement | null>(null);
// Custom circular boundary
const circularBounds = () => {
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const radius = 200;
return [
[centerX - radius, centerY - radius],
[centerX + radius, centerY + radius],
] as [[number, number], [number, number]];
};
// Dynamic boundary based on time
const timeBounds = () => {
const time = Date.now() / 1000;
const padding = Math.sin(time) * 50 + 100;
return [
[padding, padding],
[window.innerWidth - padding, window.innerHeight - padding],
] as [[number, number], [number, number]];
};
useDraggable(circular, [bounds(circularBounds)]);
useDraggable(time, [bounds(timeBounds)]);
return (
<div>
<div ref={setCircular}>Circular boundary</div>
<div ref={setTime}>Animated boundary</div>
</div>
);
}
Dynamic Boundaries
For boundaries that change during dragging, control when they’re recalculated:
bounds(boundaryFunction, (ctx) => {
// Recompute on every drag event
return ctx.hook === 'drag';
});
bounds(boundaryFunction, (ctx) => {
// Only recompute at start and end
return ctx.hook === 'dragStart' || ctx.hook === 'dragEnd';
});
How It Works
The bounds plugin calculates the allowed movement area and clamps the proposed position:
- Boundary calculation - Runs the boundary function to get
[[x1, y1], [x2, y2]]
- Element positioning - Considers the element’s size and current position
- Movement clamping - Limits
proposed.x
andproposed.y
to stay within bounds
The boundary function is called:
- On drag start (always)
- During drag (if
shouldRecompute
returns true) - On drag end (if
shouldRecompute
returns true)
API Reference
function bounds(
boundsFn: () => [[number, number], [number, number]],
shouldRecompute?: (ctx: {
hook: 'dragStart' | 'drag' | 'dragEnd';
}) => boolean,
): Plugin;
Parameters:
boundsFn
- Function returning[[x1, y1], [x2, y2]]
coordinatesshouldRecompute
- When to recalculate boundaries (default: drag start only)
BoundsFrom utilities:
BoundsFrom.viewport(padding?)
- Browser viewportBoundsFrom.parent(padding?)
- Parent elementBoundsFrom.element(el, padding?)
- Specific elementBoundsFrom.selector(selector, padding?, root?)
- Element by selector
Returns: A plugin object for use with draggable.