wrapType
Text on a surface,
still real DOM.
CSS transforms can fake 3D. wrapType does it properly — distributing real DOM text elements across the surface of a sphere, cylinder, torus, or flat plane using Three.js's CSS3DRenderer. Variable fonts, CSS animations, and every other Liiift tool compose naturally because the characters are actual HTML, not canvas pixels.
Interactive demo
How it works
Real DOM on every surface
Three.js's CSS3DRenderer places HTML elements in 3D space using CSS perspective and matrix3d transforms. Each character is its own element, oriented so its face points along the surface normal at that point.
Surface geometry, not a canvas
Character positions are computed analytically from the shape equations — no UV unwrapping or texture atlases. Spheres use latitude-band distribution scaled by circumference; cylinders and tori follow arc-length parameterisation.
Four fill modes
Cover tiles the entire surface. Flow runs a single band around the circumference or equator. Full-width fills the widest pass. Full-height runs pole-to-pole. Text always repeats to fill the chosen region.
Composes with everything
Because characters are real DOM, font-variation-settings, CSS animations, hover states, and other Liiift tools all work without any special integration. The renderer re-mounts the scene when props change.
Custom meshes
Drop any .glb, .gltf, or .obj file onto the demo above. wrapType samples the surface with MeshSurfaceSampler, orients each character along the local normal, then auto-scales to fit the scene radius.
Prefer GLB over OBJ
GLB is a self-contained binary bundle — no separate .mtl or texture files. Normals, materials, and the scene hierarchy are all embedded. OBJ works but ships without normals by default; wrapType computes them automatically when absent.
One mesh, merged geometry
wrapType picks the first Mesh it finds in the scene graph. For multi-part models, merge all objects into a single mesh before exporting so the full surface is sampled, not just one component.
Keep polygon count reasonable
5,000–50,000 triangles is plenty. Higher counts do not improve text placement quality — MeshSurfaceSampler distributes characters area-proportionally regardless. Lighter meshes load and parse faster in the browser.
Scale and coordinate system
GLTF and OBJ both use Y-up, right-handed axes. wrapType auto-fits the bounding box to its radius option, so absolute model scale does not matter. Recalculate normals in your DCC tool after any non-uniform scaling to keep character orientation correct.
Usage
TypeScript + React · Vanilla JS
React component
import { WrapTypeScene } from '@liiift-studio/wraptype'
<WrapTypeScene
text="Typography is the art and technique of arranging type"
shape="sphere"
fill="cover"
fontSize={13}
color="rgba(220,210,255,0.85)"
autoRotate
style={{ width: '100%', height: '500px' }}
/>React hook
import { useWrapType } from '@liiift-studio/wraptype'
const { ref } = useWrapType({
text: 'Typography is the art and technique of arranging type',
shape: 'cylinder',
fill: 'flow',
})
<div ref={ref} style={{ width: '100%', height: '500px' }} />Vanilla JS
import { getCharPositions, createWrapScene } from '@liiift-studio/wraptype'
const container = document.getElementById('scene')
const positions = getCharPositions({
text: 'Typography is the art and technique of arranging type',
shape: 'torus',
fill: 'cover',
})
const scene = createWrapScene(container, positions, { autoRotate: true })
// Later:
scene.destroy()Options
| Option | Default | Description |
|---|---|---|
| text | — | The string to distribute across the surface. Repeats to fill. |
| shape | 'sphere' | 'sphere' · 'cylinder' · 'torus' · 'plane' |
| fill | 'cover' | 'cover' · 'flow' · 'full-width' · 'full-height' |
| fontSize | 14 | Character font size in px. |
| color | '#fff' | CSS color applied to every character element. |
| radius | 300 | Surface radius in scene units. |
| autoRotate | false | Continuously rotate the scene. |
| autoRotateSpeed | 1.0 | Rotation speed multiplier. |
| camera | 'orbit' | 'orbit' (drag to rotate, scroll to zoom) · 'fixed' |
| charAdvanceRatio | 0.62 | Fraction of fontSize used as character advance width. |
| lineHeightRatio | 1.4 | Line height multiplier relative to fontSize. |