No WebGL · No canvas · No images
A bouquet folded out of a few hundred flat divs, every petal a plane rotated into space with CSS transforms.
drag to turn it
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.
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));
}
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
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.