A 90s sticky hand toy built with Verlet integration. Drag the handle or the hand: it sticks to walls, stretches slime threads when pulled off, and vibrates on impact.
Sticky hand toys are 90s vending machine loot. Gummy rubber, a string, you throw it at a wall and it sticks. Stretch too far and it peels off trailing threads. The physics is programmable: stiffness, gravity, bounciness are just numbers.
Drag the green handle at the top or the hand at the bottom. Fling the hand at a wall. Pull it off slowly.
Mano Loca: sticky hand physics
// Verlet step: position remembers where it was.// Difference between current and previous IS the velocity.points.forEach(p => { if (p.stuck || p === grabbed) return; const vx = (p.x - p.oldX) * FRICTION; const vy = (p.y - p.oldY) * FRICTION; p.oldX = p.x; p.oldY = p.y; p.x += vx; p.y += vy + GRAVITY;});// Constraint pass, 25x per frame for stabilityfor (let iter = 0; iter < 25; iter++) { for (let i = 0; i < points.length - 1; i++) { const a = points[i], b = points[i + 1]; const dx = b.x - a.x, dy = b.y - a.y; const dist = Math.sqrt(dx * dx + dy * dy); const correction = (REST_DIST - dist) / dist * STIFFNESS; if (!a.stuck) { a.x -= dx * correction * 0.5; a.y -= dy * correction * 0.5; } if (!b.stuck) { b.x += dx * correction * 0.5; b.y += dy * correction * 0.5; } }}
xNew position. Where the point ends up this frame.+=Add to the current position and store it back. Runs every frame.(x − xPrev)Inertia. Current position minus previous position is the implicit velocity — no separate variable needed. + Inertia and forces are additive. The point moves by both at once.axAcceleration: gravity, friction, any force applied this frame.
Hover each part.
Each point stores its current position and its previous position. The difference is velocity. No separate velocity variable needed. At each frame: move by that difference, apply gravity, then pull all the constraint distances back toward their rest length.
The constraint pass runs 25 times per frame. One iteration would leave the rope wobbly. 25 makes it feel like rubber. The stiffness constant (0.95) scales how aggressively each constraint pushes back: at 1.0 it is rigid, at 0.5 it stretches and bounces.
The sticking mechanic
When the hand crosses within 30px of any edge, it locks to that position. While stuck, it follows the handle with a 5% lerp: enough to show the tension, not enough to pull free. It only peels off when the rope is stretched past 2.5x its natural length, or when you grab the handle and pull it past 90% of that limit.
The slime threads spawn at the moment of separation: 3 to 6 quadratic curves from the stuck position to the hand, each with a random midpoint offset and thickness. They decay over about 50 frames. The blobs are small filled arcs at the midpoint, just one extra arc() call per thread.
The vibration on impact (navigator.vibrate(30)) fires on mobile when the hand sticks. 30ms. Most people don’t expect it. That tap is what makes it feel like a real surface.
Tuning the feel
Parameter
Default
Lower
Higher
GRAVITY
1.4
Floaty, slow drop
Heavy, fast drop
FRICTION
0.97
Damps quickly
Swings forever
STIFFNESS
0.95
Stretchy, elastic
Rigid, snaps back
SEGMENTS
16
Short, stubby rope
Long, heavy tail
REST_DIST
18px
Compact coil
Loose, wide swing
Slime is the same code with STIFFNESS: 0.7 and FRICTION: 0.99. Rubber band is STIFFNESS: 0.99 and GRAVITY: 0.4. One set of numbers, very different personality.