Skip to content

Performance

polycss renders everything as real DOM elements — every polygon of every mesh is an HTML element with CSS transforms. That gives you DevTools inspection, DOM events, and CSS styling on every polygon, but it means performance scales with element count. Understanding how to manage that count is key to building smooth scenes.

Live demo — polygon count vs. performance

Section titled “Live demo — polygon count vs. performance”

The sphere below starts at subdivision level 3 (320 triangles). Bump subdivisions up to 4 (1,280) or 5 (5,120) and watch the frame rate drop. This illustrates how quadrupling the subdivision level quadruples the DOM node count — and rendering cost.

Loading…

The dominant rendering cost in polycss is style recalc + layout (not paint or compositing). Each camera rotation triggers a CSS class change on the scene root, which re-evaluates descendant selectors. The more DOM nodes, the heavier this recalc.

Confirmed during benchmarking on a 10k-triangle mesh with auto-rotate:

  • Scripting: ~579ms / 7s (mostly React re-renders)
  • Rendering (style recalc + layout): ~2,130ms / 7s — the dominant cost

The merge attribute on <poly-scene> (or merge prop on PolyScene) controls how aggressively the engine consolidates coplanar adjacent polygons.

No merging. Every parsed polygon is its own DOM element. Full per-polygon control — every polygon can be individually targeted with CSS selectors, event listeners, or animations.

Best for: small meshes, interactive polygons, scenes where each polygon needs distinct behavior.

Merges coplanar adjacent polygons that share the same material (color + texture). The engine finds contiguous flat regions and combines them into single, larger elements.

Best for: architectural meshes with large flat surfaces (walls, floors, ceilings), where many coplanar same-material triangles can be collapsed without visual change.

Limitations:

  • Per-polygon DOM addressing is lost for merged groups.
  • UV-textured triangles can only merge with other triangles sharing the same texture and coplanar normal.
<!-- Vanilla -->
<poly-scene merge="auto">
<poly-mesh src="/cottage.glb"></poly-mesh>
</poly-scene>
// React
<PolyScene merge="auto">
<PolyMesh src="/cottage.glb" />
</PolyScene>
// Vue
<PolyScene merge="auto">
<PolyMesh src="/cottage.glb" />
</PolyScene>
ModeDOM nodesPer-polygon controlBest use case
"off"MostFullSmall meshes, interactive polygons
"auto"FewerLost for merged groupsDense architectural meshes, large flat surfaces

Start with the default ("off") and switch to "auto" if you notice rendering lag on dense meshes.

The single most effective way to reduce polygon count is to choose a smaller targetSize when parsing. This scales the model to fewer world-space units, and the parser (especially for OBJ) can skip triangles below the minimum renderable size.

<!-- Vanilla -->
<poly-mesh src="/castle.obj" target-size="30"></poly-mesh> <!-- fewer polygons → faster -->
<poly-mesh src="/castle.obj" target-size="80"></poly-mesh> <!-- more detail -->
// React / Vue
<PolyMesh src="/castle.obj" options={{ targetSize: 30 }} />
<PolyMesh src="/castle.obj" options={{ targetSize: 80 }} />

Generated atlas pages default to one bitmap pixel per CSS pixel. For large textured meshes, set atlasScale below 1 to reduce atlas memory and PNG encode time while keeping DOM geometry and hit areas unchanged.

<!-- Vanilla -->
<poly-scene atlas-scale="0.5">
<poly-mesh src="/cottage.glb"></poly-mesh>
</poly-scene>
// React / Vue
<PolyScene atlasScale={0.5}>
<PolyMesh src="/cottage.glb" />
</PolyScene>
// Or per mesh:
<PolyMesh src="/cottage.glb" atlasScale={0.5} />

Use 0.5 or 0.75 for distant or dense assets; keep 1 for close-up inspection.

UV-textured polygons share generated atlas blob URLs (created during the canvas rasterization pass at mount). These are revoked on unmount. Keep these rules in mind:

  • Textured meshes do a one-time atlas generation pass at mount; very large texture footprints can still cost memory and startup time.
  • Call dispose() on ParseResult (or useMesh result) when you’ve loaded the mesh imperatively.
  • The mesh element (<poly-mesh> / <PolyMesh>) handles disposal automatically on unmount or src change.