Attachments and pinning
Pin complete edges, corners, partial edges, or exact simulation-grid coordinates.
attachment determines which particles have zero inverse mass. Pinned particles remain at their original cloth positions while forces and constraints move the rest of the grid.
TypeScript shape
The public type is a discriminated union. It is exported from both flag-cloth and flag-cloth/react.
type EdgeAttachment = 'left' | 'right' | 'top' | 'bottom';
type CornerAttachment =
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';
type GridPoint = readonly [x: number, y: number];
interface PartialEdgeAttachment {
edge: EdgeAttachment;
every?: number; // default: 1
offset?: number; // default: 0
}
interface PointAttachment {
points: ReadonlyArray<GridPoint>;
}
type Attachment =
| EdgeAttachment
| CornerAttachment
| 'none'
| PartialEdgeAttachment
| PointAttachment;Complete edges, corners, and no pins
attachment: 'left' // default: the complete left edge
attachment: 'right'
attachment: 'top'
attachment: 'bottom'
attachment: 'top-left' // one corner particle
attachment: 'bottom-right'
attachment: 'none' // every particle is freeThe corner names describe the cloth in its initial, undeformed orientation.
Partial edges
Use every to keep one pin out of every N particles and offset to choose where the sequence begins.
attachment: {
edge: 'left',
every: 2,
}
attachment: {
edge: 'top',
every: 3,
offset: 1,
}For left and right, the offset counts downward from the top particle. For top and bottom, it counts rightward from the left particle. every is floored and clamped to at least 1; offset is floored and clamped to at least 0.
With segments: { x: 8, y: 4 }, the top edge contains particle coordinates [0, 0] through [8, 0]. { edge: 'top', every: 3, offset: 1 } pins [1, 0], [4, 0], and [7, 0].
Custom grid coordinates
Custom points use simulation-grid coordinates—not pixels, UVs, CSS dimensions, or world positions.
[0, 0] [segments.x, 0]
top-left ─────────────────── top-right
│ │
│ [x, y] │
│ │
bottom-left ───────────────── bottom-right
[0, segments.y] [segments.x, segments.y]import type { Attachment } from 'flag-cloth';
const attachment = {
points: [
[0, 0], // top-left
[0, 10], // halfway down a 20-row grid
[4, 4], // an interior point
],
} satisfies Attachment;
const flag = createFlagCloth({
container,
texture: '/flag.webp',
segments: { x: 32, y: 20 },
attachment,
});Coordinates are rounded to the nearest integer. Points outside 0...segments.x or 0...segments.y are ignored. Repeated points are harmless.
React
The React component accepts the same Attachment union. There is no reduced React-only attachment type.
import { FlagCloth, type Attachment } from 'flag-cloth/react';
const mastPins = {
points: [[0, 0], [0, 5], [0, 10], [0, 15], [0, 20]],
} satisfies Attachment;
export function Flag() {
return (
<FlagCloth
texture="/flag.webp"
segments={{ x: 32, y: 20 }}
attachment={mastPins}
canvasWidth="100%"
canvasHeight={520}
/>
);
}Changing pins at runtime
await flag.setOptions({
attachment: { edge: 'top', every: 2 },
});Changing attachment clears the old pin mask, applies the new one, and updates particle inverse masses without rebuilding topology or the renderer. Newly pinned points snap to their original grid positions. If React receives a new attachment prop, its wrapper calls the same setOptions() path.
Pinned particles cannot normally be dragged. Set interaction.allowPinned: true if a pointer should temporarily move them; they return to their pinned positions after release.