@materials3d/element

Drop-in Materials3D scene for Vue, Svelte, or plain HTML: the custom element
945
Install
npm install @materials3d/element
Latest Version:0.6.0
License:MIT
Last Updated:Oct 1, 2026
Links: GitHub  ·  npm
Maintainer: amabush

@materials3d/element

The <materials-3d> custom element: a drop-in Materials3D scene for Vue, Svelte or plain HTML.

pnpm add @materials3d/element three
<script type="module">
  import "@materials3d/element";
</script>

<materials-3d
  preset="skewer"
  poster="/hero.webp"
  min-size="520"
  style="display:block; width:100%; height:100vh"
></materials-3d>

Light DOM, display: block, self-registering on import (guarded, so importing under Node during SSR is a no-op). Poster first and lazy, with three code-split out of the initial load. register(tag) registers the class under another tag name.

Attributes

attribute notes
preset a shipped preset name
src URL of a config JSON, for example one of the files in gallery/
config inline JSON, merged one level deep over preset and src
transparent drop the backdrop, so the scene composites over the page
paused live; toggles playback
poster, poster-fit poster-fit is fill (default, matches the canvas), cover or contain
fade-ms poster→canvas crossfade in ms; default 300, 0 swaps instantly
lazy, webgl, min-size shell options
renderer webgpu selects the experimental WebGPU/TSL engine; anything else is WebGL

Boolean attributes: present means true, "false" or "0" means false, absent means the shell default.

poster, poster-fit, fade-ms, lazy, webgl, min-size and renderer are read once at mount. Changing one on a connected element does nothing until it is disconnected and reconnected. config, src, preset, transparent and paused are live.

Properties and events

The config property (a Partial<SceneConfig>) is merged last, one level deep, for framework bindings. The read-only handle is the shell's MaterialHandle, or null before connect and after disconnect. The element is declared in HTMLElementTagNameMap, so document.querySelector("materials-3d") types as Materials3DElement and .handle typechecks without a cast.

Events: materials3d-ready (detail: the renderer) and materials3d-fallback (detail: the reason the scene stayed a poster).

const el = document.querySelector("materials-3d");
if (el) {
  el.addEventListener("materials3d-ready", async () => {
    const blob = await el.handle?.snapshot({ time: 0 });
  });
}

Capture a poster at the pixel ratio it will be shown at

snapshot() returns the canvas at its backing-store size: the element's CSS size times the device pixel ratio, capped by dprMax. If posters are generated in a headless browser, that browser's deviceScaleFactor is what decides their resolution — and it defaults to 1.

A poster captured that way is half the resolution the same canvas renders at on a 2x display. The browser stretches it to fill the element while the live canvas behind it does not, so the crossfade lands on a visible sharpening: edges tighten, and because soft edges bloom outward the poster reads as slightly heavier than the live scene rather than merely blurrier.

const page = await browser.newPage({
  viewport: { width: 1440, height: 820 },
  deviceScaleFactor: 2,
});

Two things go with it. Keep the encoder quality high enough not to ring the high-contrast edges — a lossy poster shows up as thin outlines tracing every rim. And remember a fixed-aspect poster is stretched by poster-fit while the canvas re-frames itself from camera.fit, so capture at the aspect the element is widest at. Where the two policies disagree — minVisibleWidth widens the camera but never the poster — expect a mismatch at narrow sizes; fade-ms="0" at least stops it being a dissolve.

Peer dependency: three >= 0.180 < 1. @types/three is an optional peer.

MIT.

Related Packages

@wave3d/element

Drop-in animated 3D gradient wave for Vue, Svelte, or plain HTML: the custom ele...

2,488 2
glassy-liquid

Apple-style Liquid Glass for the web: real refraction, specular light and fluid...

284 17
iconsax

Iconsax web component — 1200+ free icons out of the box, Pro icons via `iconsax...

37,176
@omni-ir/elements

: Omni-IR generative UI screens in any web framework or none (Vue, Svelte, Angul...

591 0
@skeehn/wc

Framework-agnostic Web Components for skeehn — custom elements that work in Vue,...

318 0

Version History

Version Released License
0.6.0 MIT
0.5.0 MIT
0.4.1 MIT
0.4.0 MIT
0.3.0 MIT