latest

@vctrl/viewer

NPM Downloads Storybook

A ready-to-use React component for rendering and interacting with 3D models. Built on top of Three.js and React Three Fiber.

This package is still in active development. Breaking changes may occur before the first major release.


Installation

npm install @vctrl/viewer
# or
pnpm add @vctrl/viewer

Quick start

import { useLoadModel } from '@vctrl/hooks/use-load-model'
import { VectrealViewer } from '@vctrl/viewer'
import '@vctrl/viewer/css'
 
function App() {
  const { file } = useLoadModel()
  return <VectrealViewer model={file?.model} />
}

You must import the CSS bundle (@vctrl/viewer/css) for the viewer to render correctly.


VectrealViewer props

PropTypeRequiredDescription
modelObject3DNo*The Three.js scene to display. *Optional when using the use-load-model context.
classNamestringNoAdditional CSS classes for the viewer container
theme'light' | 'dark' | 'system'NoViewer theme, default is system
enableViewportRenderingbooleanNoRender only while in viewport (default true)
boundsOptionsBoundsPropsNoScene bounds/framing behavior
cameraOptionsCameraPropsNoPerspective camera configuration
controlsOptionsControlsPropsNoOrbitControls configuration
envOptionsEnvironmentPropsNoStage and Environment component configuration
shadowsOptionsShadowsPropsNoShadow behavior configuration
popoverReact.ReactNodeNoOptional info popover slot
loaderReact.ReactNodeNoCustom loading UI
loadingThumbnailViewerLoadingThumbnailNoOptional blurred loading thumbnail
onScreenshot(dataUrl: string) => voidNoCalled when a screenshot is captured
onScreenshotCaptureReady(capture: SceneScreenshotCapture | null) => voidNoReceives capture function for on-demand screenshots

Notes on content source

  • model is optional because you can also render scene content via children.
  • Grid options are currently typed but not active in render output.

Camera options (CameraProps)

cameraOptions accepts:

type CameraProps = {
  cameras?: Array<PerspectiveCameraProps & {
    cameraId: string
    name: string
    initial?: boolean
    shouldAnimate?: boolean
    animationConfig?: {
      duration: number
      easing?: (t: number) => number
    }
  }>
}

Each camera entry extends PerspectiveCameraProps from Drei and adds viewer-specific camera switching metadata.

<VectrealViewer
  cameraOptions={{
    cameras: [
      {
        cameraId: 'default',
        name: 'Default',
        initial: true,
        shouldAnimate: true,
        animationConfig: { duration: 900 },
        position: [0, 5, 8],
        fov: 55,
        near: 0.1,
        far: 1000
      }
    ]
  }}
/>

Controls options (ControlsProps)

Based on @react-three/drei OrbitControls.

controlsOptions extends OrbitControls props and adds:

OptionTypeDescription
controlsTimeoutnumberDelay in milliseconds before controls behavior resumes
<VectrealViewer
  controlsOptions={{
    maxPolarAngle: Math.PI / 2,
    autoRotate: true,
    controlsTimeout: 2000,   // ms before auto-rotate resumes
  }}
/>

Environment options (EnvironmentProps)

Configures the @react-three/drei Stage and Environment components.

envOptions supports a typed preset system from @vctrl/core:

OptionTypeDescription
presetEnvironmentKeyPreset key such as studio-key, outdoor-noon, night-city
environmentResolution'1k' | '4k'Resolution variant for environment assets
backgroundbooleanRender environment as scene background
backgroundBlurrinessnumberBlur strength when background is enabled
backgroundIntensitynumberBackground intensity multiplier
environmentIntensitynumberLighting intensity multiplier
groundEnvironmentProps['ground']Ground-projected environment options
filesstring | string[]Custom environment files
<VectrealViewer
  envOptions={{
    preset: 'studio-key',
    environmentResolution: '1k',
    background: true,
    backgroundBlurriness: 0.2,
    environmentIntensity: 1,
    backgroundIntensity: 1,
  }}
/>

Bounds and shadows

PropTypeSummary
boundsOptionsBoundsPropsPass-through to Drei Bounds behavior
shadowsOptionsShadowsPropsUnion of accumulative and contact shadow configs

boundsOptions (BoundsProps)

BoundsProps is forwarded to Drei Bounds. The viewer defaults are:

OptionDefault
fittrue
cliptrue
margin1.5
maxDuration0

Example:

<VectrealViewer
  boundsOptions={{
    fit: true,
    clip: true,
    margin: 1.25,
    maxDuration: 300
  }}
/>

shadowsOptions (ShadowsProps)

ShadowsProps is a discriminated union using type.

Contact shadows (type: 'contact')

Viewer defaults:

OptionDefault
type'contact'
opacity0.4
blur0.1
scale5
color'#000000'
smoothtrue

Commonly-used ContactShadows fields you can pass:

OptionType
opacitynumber
blurnumber
scalenumber | [number, number]
farnumber
resolutionnumber
colorstring
framesnumber

Example:

<VectrealViewer
  shadowsOptions={{
    type: 'contact',
    opacity: 0.45,
    blur: 1.8,
    scale: 6,
    far: 12,
    resolution: 1024,
    color: '#111111'
  }}
/>

Accumulative shadows (type: 'accumulative')

Viewer defaults:

OptionDefault
type'accumulative'
temporalfalse
frames30
alphaTest0.35
opacity1
scale10
resolution1024
colorBlend2
color'#000000'

Nested light defaults (shadowsOptions.light):

OptionDefault
intensity1
amount5
radius7.5
ambient0.5
position[5, 10, 5] (or auto-calculated from scene bounds)

Example:

<VectrealViewer
  shadowsOptions={{
    type: 'accumulative',
    temporal: true,
    frames: 40,
    alphaTest: 0.4,
    opacity: 0.95,
    scale: 12,
    resolution: 1024,
    colorBlend: 2,
    color: '#000000',
    light: {
      amount: 6,
      radius: 7,
      ambient: 0.5,
      intensity: 1,
      bias: 0.0001
    }
  }}
/>

Screenshot callbacks

VectrealViewer supports two screenshot-related callbacks:

  • onScreenshot(dataUrl) receives a PNG data URL each time a screenshot is captured.
  • onScreenshotCaptureReady(capture) gives you a capture function that can be stored and called from external UI.

The callback types are exported from @vctrl/viewer as SceneScreenshotCapture and SceneScreenshotOptions.

SceneScreenshotOptions:

OptionTypeDescription
widthnumberOutput width in pixels
heightnumberOutput height in pixels
mimeType'image/jpeg' | 'image/webp'Output format
qualitynumberImage quality hint for lossy output
mode'auto-fit' | 'viewport'Capture strategy

Integration with @vctrl/hooks

The viewer is designed to be used alongside @vctrl/hooks. When you wrap your app with the ModelProvider context from @vctrl/hooks, the viewer automatically picks up the loaded model:

import { ModelProvider, useLoadModel } from '@vctrl/hooks/use-load-model'
import { VectrealViewer } from '@vctrl/viewer'
import '@vctrl/viewer/css'
 
function Scene() {
  // No `model` prop needed — viewer reads from context
  return <VectrealViewer />
}
 
export default function App() {
  return (
    <ModelProvider>
      <Scene />
    </ModelProvider>
  )
}

Development

# Build
pnpm nx build vctrl/viewer
 
# Test
pnpm nx test vctrl/viewer
 
# Storybook
pnpm nx storybook vctrl/viewer

Notes

  • Grid configuration is not currently active in VectrealViewer render flow.


Source

The full source and README live in packages/viewer/.