# Model Viewer

> A product-style 3D model viewer that frames itself, lights itself with a studio HDRI and floats over a soft contact shadow.

Load a glb, gltf, fbx or obj file and it is centered, scaled to fit and lit automatically. Drag to turn it with momentum, hover to make it lean toward the cursor, pinch or hold Ctrl/⌘ and scroll to zoom, and double-click to return to the starting view.

<demo-model-viewer>



</demo-model-viewer>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/model-viewer.json"
```

## Usage

```vue
<script setup lang="ts">
  import { useTemplateRef } from 'vue';
  import ModelViewer from '~/components/ui/model-viewer/ModelViewer.vue';
  import type { ModelViewerExpose } from '~/components/ui/model-viewer/types';

  const viewer = useTemplateRef<ModelViewerExpose>('viewer');

  function download() {
    const image = viewer.value?.capture('image/png');
    if (image) window.open(image);
  }
</script>

<template>
  <ModelViewer
    ref="viewer"
    url="/models/sneaker.glb"
    width="100%"
    :height="500"
    environment="studio"
    intro="rise"
    :start-angle="-35"
    :tilt="12"
  />
</template>
```

Built on plain three.js: an HDRI from the preset list lights the model, a neutral tone mapper keeps product colors true, and a blurred depth pass from below draws the contact shadow. The canvas only renders while something moves, and it pauses offscreen. glTF files compressed with Draco or Meshopt load as-is.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        url
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      URL of the model. Supports glb, gltf, fbx and obj.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      Width of the viewer. Numbers are pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        height
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      Height of the viewer. Numbers are pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        environment
      </code>
    </td>
    
    <td>
      <code>
        ModelViewerEnvironment
      </code>
    </td>
    
    <td>
      <code>
        'studio'
      </code>
    </td>
    
    <td>
      Lighting environment: studio, city, sunset, dawn, warehouse, apartment, lobby, night, park, forest or <code>
        'none'
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        environment-intensity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Strength of the environment lighting and reflections.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        exposure
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Overall brightness of the render.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        'rise' | 'spin' | 'fade' | 'none'
      </code>
    </td>
    
    <td>
      <code>
        'rise'
      </code>
    </td>
    
    <td>
      How the model appears once it has loaded.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-rotate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Slowly turns the model while idle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-rotate-speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.4
      </code>
    </td>
    
    <td>
      Speed of the idle rotation. Negative values turn the other way.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-rotate-delay
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Seconds after you let go before the idle rotation eases back in.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drag-rotate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Drag to turn the model, with momentum when you let go.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        turntable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Limits dragging to left and right, like a product turntable.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        zoom
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Pinch or hold Ctrl/⌘ and scroll to zoom. Plain scrolling still scrolls the page.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        min-zoom
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.7
      </code>
    </td>
    
    <td>
      How far out you can zoom.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        max-zoom
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      How far in you can zoom.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hover-tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.15
      </code>
    </td>
    
    <td>
      How much the model leans toward the cursor. <code>
        0
      </code>
      
       turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        float
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.3
      </code>
    </td>
    
    <td>
      Gentle floating above the shadow. <code>
        0
      </code>
      
       turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shadow
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Soft contact shadow under the model.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shadow-opacity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.45
      </code>
    </td>
    
    <td>
      Darkness of the shadow.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fit
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.8
      </code>
    </td>
    
    <td>
      How much of the viewer the model fills. It is framed automatically.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        offset-x
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Moves the model sideways, from -1 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        offset-y
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Moves the model up or down, from -1 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        start-angle
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -30
      </code>
    </td>
    
    <td>
      Starting angle around the model, in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Starting tilt toward the viewer, in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reset-on-double-click
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Double-click to return to the starting view.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the container.
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        model-loaded
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Fired once the model has loaded.
    </td>
  </tr>
</tbody>
</table>

## Exposed

<table>
<thead>
  <tr>
    <th>
      Method
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        capture
      </code>
    </td>
    
    <td>
      <code>
        (type?: string) => string | null
      </code>
    </td>
    
    <td>
      Returns the current frame as an image data URL.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The canvas is exposed as an image with a label that describes the gestures. Put a text description of the product next to the viewer, as the 3D view carries no text.
- Plain wheel scrolling is never captured, so the page keeps scrolling; zoom needs Ctrl/⌘ or a pinch.
- With `prefers-reduced-motion: reduce`, the intro, floating and idle rotation are turned off. Dragging still works.
- Ported from [React Bits](https://reactbits.dev/components/model-viewer) by David Haz.
