Pular para o conteúdo

Model viewer

components specs/components/model-viewer.kmd

Interactive 3D model viewer — embed a glTF/GLB mesh that the user can orbit, zoom and (on capable devices) place in AR. Self-hosted viewer (no third-party SaaS), poster-first lazy loading, reduced-motion + image fallback. No Material parity (M3 has no 3D viewer); Koder fills the gap for product catalogs, hardware showcases and Hub hero assets.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Model viewer

Facet Visual + Integration do Koder Design. No Material 3 equivalent — Koder addition. Built on the self-hosted <model-viewer> web component (glTF) + native quick-look on mobile.

KoderModelViewer embeds a real 3D mesh the user can orbit, zoom and inspect, with an optional AR "place in your space" mode. It is the only component that renders true geometry, so it is the on-ramp for the 3D / depth.kmd story on flat surfaces.

Use cases

  • Product catalog — papelaria e-commerce (Crescer / Vivver) item preview; rotate before buy.
  • Hardware / device showcase — Koder Kodix device, peripheral.
  • Hub hero asset — an app's 3D icon or mascot.
  • Spec/asset preview — inspect a 3D model bundled with a product.

R1 — Formats

FormatRole
glTF / GLBCanonical. GLB (binary) preferred for single-file delivery. Draco / meshopt compression REQUIRED above 1 MB.
USDZCompanion file for iOS/visionOS AR Quick Look only. Generated from the GLB; never the source of truth.
Poster imageMANDATORY. A static render (.webp / .png / .svg) shown before/instead of the live model (R3).

Self-hosted per self-hosted-first.kmd: the viewer runtime is bundled locally and served under script-src 'self'. No CDN, no model-hosting SaaS. Canonical web impl: <koder-model-viewer> (the koder-model-viewer.js wrapper) over the self-hosted @google/model-viewer engine vendored at /assets/vendor/model-viewer/. DRACO / meshopt decoders, if used, MUST also be self-hosted (they default to a Google CDN) — prefer uncompressed glTF for small assets to avoid that external decoder path.

R2 — Interaction

GestureAction
Drag / arrow keysOrbit
Wheel / pinchZoom (clamped min/max)
Two-finger / right-dragPan (opt-in; off by default)
Auto-rotateOptional idle spin; off under prefers-reduced-motion

Auto-rotate, when on, spins slowly (≤ 30°/s, matching the XR motion- sickness ceiling, xr-preview.kmd § R8) and stops on interaction.

R3 — Loading & performance

Poster-first, always lazy:

  1. Render the poster image immediately (correct aspect, no layout shift — like cards.kmd § R5).
  2. Defer the viewer runtime + model until the element enters the viewport (IntersectionObserver) or the user activates the poster.
  3. Show a determinate progress indicator while the GLB downloads.

Budgets:

BudgetTarget
GLB transfer size≤ 2 MB recommended; > 5 MB blocks release (warn)
First interactive< 1.5 s on mid-range mobile after activation
Frame rate60 fps orbit; degrade mesh LOD before dropping frames
Wrapper JS (eager)≤ 5 KB gzipped (koder-model-viewer.js) — safe on the critical path
Engine JS (lazy)@google/model-viewer ≈ 285 KB gzipped (three.js-based); MUST be lazy-loaded on poster activation / viewport entry, never on the critical path, loaded once + shared

R4 — Lighting, depth & theme

  • The scene's directional light reads the rig in lighting.kmd § R5 (azimuth/elevation) so a model and the cards around it are lit from the same angle.
  • Ground/contact shadow uses the umbra+penumbra pair (lighting.kmd § R2).
  • The viewer sits at a depth level like any surface (depth.kmd); its container shadow follows elevation.
  • Environment / IBL swaps with light/dark mode (brighter neutral env in light; dimmer in dark). Background defaults to transparent (inherits surface) — never a baked-in colour.

R5 — AR mode

"View in your space" button, shown only when supported:

PlatformMechanism
AndroidScene Viewer (GLB)
iOS / iPadOS / visionOSAR Quick Look (USDZ)
WebXR browsersWebXR session (opt-in)
UnsupportedButton hidden — never a dead control

AR is not themed (uses OS-native UI) and is never the only way to see the model.

R6 — Accessibility

  • aria-label / alt REQUIRED — a text description of the model (the model is never the sole carrier of information; pair with caption or spec text).
  • Keyboard orbit via arrow keys; viewer is focusable with a visible ring.
  • Poster image has its own alt.
  • prefers-reduced-motion → no auto-rotate, no entry animation; the poster + manual orbit remain.
  • Respects forced-colors — the chrome (buttons, progress) falls back to system colours; the 3D canvas is exempt.

R7 — Platform binding (koder_kit)

  • Web / Flutter Web<koder-model-viewer src= poster= alt=> wrapping the self-hosted <model-viewer> element.
  • Flutter mobile/desktopKoderModelViewer widget (Filament / model_viewer_plus backend); same poster-first + AR contract.
  • Shared attributes: src, usdz, poster, alt, auto-rotate, ar, camera-controls, max-zoom.

R8 — Forbidden patterns

  • ❌ No poster (blank box during load / on failure).
  • ❌ Auto-rotate that ignores prefers-reduced-motion or never stops.
  • ❌ Model as the only source of essential info (R6).
  • ❌ Unoptimized multi-MB GLB shipped without Draco/meshopt (R3).
  • ❌ Third-party model-hosting SaaS or CDN runtime (R1, self-hosted).
  • ❌ Baked background colour that ignores light/dark (R4).
  • ❌ Blocking the main thread on load instead of lazy + progress (R3).

T1-T5 — Tests

T1 — Poster fallback: with JS disabled / before activation, the poster renders at the correct aspect with valid alt. T2 — Lazy load: the GLB is not fetched until the element is in viewport or activated (network assertion). T3 — Reduced-motion: with prefers-reduced-motion, no auto-rotate and no entry animation occur. T4 — AR gating: AR button is absent on a platform reporting no AR support (no dead control). T5 — Lighting coherence: scene directional light angle equals --kds-light-azimuth/elevation within 1° (lighting.kmd § T3).

Live demo

  • themes/depth.kmd — the viewer sits on the z-axis like any surface
  • themes/lighting.kmd — shared light rig (R4) + cast shadow
  • themes/motion/physics.kmd — reduced-motion contract (R2/R6)
  • components/cards.kmd — product card hosting a model viewer (R5 media)
  • policies/self-hosted-first.kmd — bundled runtime, no SaaS (R1)

Referências