SH
I
EMO
← All projects

Rick's Closet.

A 3D clothing store where you walk into a wardrobe and the shirts move like fabric.

Rick's Closet on desktop
Role
Design & development
Timeline
10 days
Type
Concept project
Stack
Next.jsTypeScriptThree.js

The challenge

Online clothes shopping is a grid of flat photos. I wanted browsing to feel like flicking through a real rail: walk into a wardrobe, slide garments along, and watch them sway when you touch them.

Real cloth physics means hundreds of points per garment, updated many times a second, on top of a full 3D scene with lighting and shadows. Doing that on the main thread would stall every interaction, and most everyday phones don't have memory to spare.

How it works

How a product photo becomes a shirt you can push around:

Build timeIn the browser
Garment photo, background removed
Prep script trims the hanger and fits it to 1024×1280
64×80 alpha mask read from the photo
Cloth grid shaped by the mask: 20×25 points, 14×18 on phones
Stretch, shear and bend constraints
Web worker steps the physics at 120 Hz
Main thread bends the shirt mesh every frame
Build timeIn the browser
Garment photo, background removed
Prep script trims the hanger and fits it to 1024×1280
64×80 alpha mask read from the photo
Cloth grid shaped by the mask: 20×25 points, 14×18 on phones
Stretch, shear and bend constraints
Web worker steps the physics at 120 Hz
Main thread bends the shirt mesh every frame

Key decisions

  1. 01

    Turn product photos into cloth

    Each garment starts as a photo. A prep script strips the hanger and fits it to a fixed canvas, then in the browser a 64×80 alpha mask shapes a grid of points held together by stretch, shear and bend constraints. Only the garments near the centre of the rail are simulated, at most three at a time.

  2. 02

    Run the physics off the main thread

    The simulation lives in a web worker, stepping at 120 Hz with six constraint passes. Each frame the main thread sends the pointer and hanger positions and gets the cloth back, so dragging the rail never waits on the physics.

  3. 03

    A lite mode for low-memory phones

    Phones that report 4 GB of memory or less get a lighter scene: the render resolution caps at 1.5×, and bump maps, sheen and clearcoat are stripped from every fabric. Each room is its own route, and the next room's assets preload at low priority.

Code from components/rack/device.ts:
import { MeshPhysicalMaterial, type MeshPhysicalMaterialParameters } from "three";

const memory =
  typeof navigator === "undefined" ? undefined : (navigator as Navigator & { deviceMemory?: number }).deviceMemory;
export const LOW_MEMORY = memory !== undefined && memory <= 4;
export const DPR: [number, number] = LOW_MEMORY ? [1, 1.5] : [1, 2];

const BUMP = new Set(["bumpMap", "bumpScale"]);

export function flat<T extends object>(params: T): T {
  return LOW_MEMORY ? (Object.fromEntries(Object.entries(params).filter(([key]) => !BUMP.has(key))) as T) : params;
}

const EXTRAS = /^(clearcoat|sheen)/;

export function physicalMaterial(params: MeshPhysicalMaterialParameters) {
  return new MeshPhysicalMaterial(
    LOW_MEMORY ? Object.fromEntries(Object.entries(flat(params)).filter(([key]) => !EXTRAS.test(key))) : params,
  );
}

Decision 03, in code

components/rack/device.ts

  • Lines 3–4

    Reads the device's memory, where the browser exposes it.

  • Lines 5–6

    4 GB or less turns lite mode on and caps the render resolution at 1.5× instead of 2×.

  • Lines 10–19

    Every fabric material goes through these, so lite devices never build bump maps, clearcoat or sheen.

Results

  • 5saverage first load
  • 500cloth points per garment, 252 on phones
  • 120 Hzphysics steps, off the main thread
  • 3rooms: clothes rail, shoe wall and watch box

Screens

Rick's Closet on desktop
Rick's Closet on mobile