skip to content
lav.ren / tinker / paper-deform

Paper deformation

Grab a card corner and fold it like real paper. CSS clip-path morphing with spring physics — 6 approaches tried, pure CSS won.

Date
2026
Stack
ReactCSS

Cards on this site are styled to look like paper on a desk. This experiment takes that metaphor further: grab any corner and the card folds like a real sheet of paper.

The final approach uses pure CSS — no WebGL, no texture capture. The card’s entire shape is a clip-path: path() with a quadratic bezier that morphs from a rounded corner into a straight diagonal fold line. A sibling flap element shows the paper’s back side. Spring physics drive the animation.

Hello

Grab my corners to peel — pure CSS, no WebGL.

Projects

Solar garden simulator, AoC solver, ink playground — built with Astro + React.

About

Full-stack engineer. Paper by day, paper by night. Based in Belgium.

Blog

Thoughts on web performance, developer tooling, and creative coding experiments.

Controls

DirectSpringyToy
0.00

How it works

  1. LIFT — drag a corner and it straightens from a rounded arc to a straight diagonal (Q bezier morph)
  2. FOLD — a small fold-over triangle appears on the crease, growing from a sliver to fill the diagonal
  3. PULL — the fold-over and crease grow together as you drag further
  4. Release — damped spring snaps everything back to the default state

The fold line is always a 45° diagonal. The flap’s hypotenuse is geometrically locked to the clip’s diagonal — no gap. Pre-folded cards start at different stages to show the range.


The journey: 6 approaches tried

Getting here wasn’t straight­forward. We tried 6 different approaches — from CSS clip-path to Three.js mesh deformation and back. Each mini-demo below shows what the approach looked like (hover to interact, or click where noted):

Hello
A sample card for testing the fold effect.
1

Empty Overlay Divs

Failed

Applied clip-path to empty <div> overlays inside the card. But clip-path on an empty transparent div is invisible — the card looks completely unchanged. Nothing happens.

Hello
A sample card for testing the fold effect.
2

Child Flap (Clip Inheritance)

Failed

Applied clip-path to the card itself and placed a paper-colored flap as a child element. But CSS clip-path clips ALL descendants — the child flap gets clipped along with the content. It can never escape the parent's clip boundary.

Hello
A sample card for testing the fold effect.
3

Sibling Flap (Flat Fold)

Partial

Placed the flap as a sibling element outside the card's DOM — no clip inheritance. The fold-over triangle is visible. But it's a rigid flat triangle — the text doesn't bend with the fold.

Hello
A sample card for testing the fold effect.
DOM
Hello
Text looks different...
Texture
4

Three.js Mesh Deformation

Failed

Captured the card as a Canvas texture and mapped it onto a subdivided Three.js plane. Vertex deformation created a beautiful cylindrical curl — but the texture never matched the DOM. foreignObject produced black textures. Canvas 2D manual drawing had wrong fonts and colors.

Hello
A sample card for testing the fold effect.
Hello
Slightly grey...
click to swap
5

R3F + html2canvas

Failed

Switched to React Three Fiber with html2canvas for better texture fidelity. Colors were closer but still slightly grey. A 150ms cross-fade hid the mismatch somewhat, but the transition was still noticeable.

Hello
A sample card for testing the fold effect.
6

Pure CSS Fold (Current)

Works

No WebGL, no texture capture. The card's shape is entirely clip-path: path() with a Q bezier that morphs from rounded corner → straight diagonal. A sibling flap shows the paper-back color. Three phases: LIFT (corner straightens), FOLD (flap grows on crease), PULL (both grow). The DOM is always real DOM — zero fidelity loss.