Skip to main content

Constraint System

Constraints limit how interactive components can move, enabling precise control over user interactions.

Available Constraints

horizontal

Constrains movement to a horizontal line at a specific y-coordinate.

import { horizontal } from '@wangyaoshen/locus-interaction';

// Only move along y = 100
const constraint = horizontal(100);

vertical

Constrains movement to a vertical line at a specific x-coordinate.

import { vertical } from '@wangyaoshen/locus-interaction';

// Only move along x = 50
const constraint = vertical(50);

grid

Snaps positions to a grid.

import { grid } from '@wangyaoshen/locus-interaction';

// Snap to 10x10 grid
const constraint = grid({ size: 10 });

// Snap to different x/y sizes
const constraint = grid({ sizeX: 20, sizeY: 10 });

// Snap to grid with offset
const constraint = grid({ size: 10, offsetX: 5, offsetY: 5 });

circular

Constrains movement to a circle.

import { circular } from '@wangyaoshen/locus-interaction';

// Move on circle centered at (100, 100) with radius 50
const constraint = circular([100, 100], 50);

line

Constrains movement to a line segment between two points.

import { line } from '@wangyaoshen/locus-interaction';

// Move along line from (0, 0) to (100, 100)
const constraint = line([0, 0], [100, 100]);

bounds

Constrains movement within a rectangular boundary.

import { bounds } from '@wangyaoshen/locus-interaction';

// Stay within rectangle
const constraint = bounds({
xMin: 0,
xMax: 500,
yMin: 0,
yMax: 300,
});

Composing Constraints

Use compose to combine multiple constraints. They are applied in order.

import { compose, grid, bounds } from '@wangyaoshen/locus-interaction';

// Snap to grid AND stay within bounds
const constraint = compose(
grid({ size: 10 }),
bounds({ xMin: 0, xMax: 500, yMin: 0, yMax: 300 }),
);

Using Constraints

Apply constraints when creating interactive components:

import { InteractivePoint, horizontal, bounds, compose } from '@wangyaoshen/locus-interaction';

const point = new InteractivePoint({
position: [100, 100],
constrain: compose(
horizontal(100),
bounds({ xMin: 0, xMax: 500, yMin: 100, yMax: 100 }),
),
});

Custom Constraints

Create custom constraint functions:

import { ConstraintFunction, Vector2 } from '@wangyaoshen/locus-interaction';

// Custom constraint: only allow positions where x + y < 200
const customConstraint: ConstraintFunction = (pos: Vector2): Vector2 => {
const [x, y] = pos;
if (x + y >= 200) {
// Project to the line x + y = 200
const sum = x + y;
const ratio = 200 / sum;
return [x * ratio, y * ratio];
}
return pos;
};

const point = new InteractivePoint({
position: [50, 50],
constrain: customConstraint,
});

Constraint Function Signature

type ConstraintFunction = (
position: Vector2,
options?: ConstraintOptions
) => Vector2;

interface ConstraintOptions {
previous?: Vector2; // Previous position (for velocity-based constraints)
bounds?: ViewportBounds; // Viewport bounds
}