flag-cloth

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 free

The 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.

On this page