← back Grown from divs · CSS only

No WebGL · No canvas · No images

Paper
Flowers

A bouquet folded out of a few hundred flat divs, every petal a plane rotated into space with CSS transforms.

Pick your stems ↓

drag to turn it

planes on stage: 0

The flower stall

Ten species grow here, every one built from the same hinged planes as the bouquet above. Pick what you like, as many as the wrap can hold, and it will be regrown on the spot. Hover a card to see the flower turn.

stems: 0 / 12 0 petal planes

How a flower grows from flat divs

CSS can only draw flat planes, but it can place them anywhere in space. The whole bouquet comes down to four moves, each one a class in css3d.css. The little scenes below run on the exact same classes as the bouquet above.

01 · The scene

A perspective on the scene gives depth, and preserve-3d on every node keeps children living in the same space instead of being flattened.

.c3d-scene {
  perspective: 1100px;
}
.c3d {
  transform-style: preserve-3d;
}

02 · Rotate, then push

Turn a plane with --ry, then push it out along its own axis with --oz. Eight planes, one ring. The roses do this with the golden angle instead of 45°.

<div class="c3d-face"
  style="--ry: 45deg;
         --oz: 42px">

03 · The hinge

A hinged plane pivots around its bottom edge. Lean it out with a negative --rx and it opens like a petal. The bloom slider above just scales that one angle on every petal at once.

.c3d-face--hinge {
  transform-origin: 50% 100%;
}
--rx: calc(var(--open)
      * var(--bloom) * -1)

04 · The bend

Planes are flat, petals are not. Chain short segments on the top edge of a plane, bend each joint a few degrees, and the eye reads one curved surface. Rose petals curl out, tulips curl in.

.c3d-seg {
  bottom: 100%;
  transform:
    rotateX(var(--bend));
}

Take the toolkit

The template is two small files with no dependencies: css3d.css defines the scene, nodes, faces and hinges, and css3d.js builds trees of them. Everything else here, wrap, ribbon, roses, is just data fed through those helpers.

The pipeline, one transform for everything

  • --x --y --z  position in the parent's space
  • --ry  azimuth, spin around the vertical axis
  • --rx  tilt, lean forward or back
  • --rz  twist, roll in the plane itself
  • --ox --oy --oz  push along the rotated axes
  • --s  scale, from the hinge point if hinged

Applied in that order on every node and face. Position it, aim it, push it out, size it. Because the values are custom properties, one variable set high in the tree, like --bloom, reaches every petal below it.

A daisy in nine lines

import { node, face, ring } from './css3d.js';

const flower = node(stage);
ring(12, (i, angle) => {
  face(flower, {
    ry: `${angle}deg`,
    open: '75deg',
    rx: 'calc(var(--open) * var(--bloom, 1) * -1)',
  }, 'petal', 'hinge');
});

Twelve hinged planes around a ring, opened 75°. Skin them with a gradient in CSS and you have a daisy. The full bouquet builder lives in flowers.js.