```
```vue [ref (Problematic)]
```
::
### shallowReactive for Object Properties
When you need to reactively update multiple properties independently:
```vue [shallow-reactive.vue]
```
## Best Practices and Patterns
### 1. Initial Positioning vs Animation
Use reactive props for initial positioning and template refs for animation:
```vue [best-practices.vue]
```
### 2. Computed Properties for Complex Calculations
Use computed properties for expensive calculations that shouldn't run in every frame:
```vue [computed-properties.vue]
```
### 3. Lifecycle-Based Updates
Use Vue's lifecycle hooks for performance-sensitive updates:
```vue [lifecycle-updates.vue]
```
## Common Pitfalls and Solutions
### β Pitfall 1: Reactive Animation Data
```vue [pitfall-reactive-animation.vue]
```
**Solution: Use template refs**
```vue [solution-template-refs.vue]
```
### β Pitfall 2: Deep Reactive Arrays
```vue [pitfall-reactive-arrays.vue]
```
**Solution: Non-reactive data with template refs**
```vue [solution-particle-system.vue]
```
## Performance Monitoring
:examples-performance-monitor
Use `@tresjs/leches` [built-in fpsgraph](https://tresleches.tresjs.org/misc/#fpsgraph){rel=""nofollow""} for monitoring performance in your TresJS applications. This control displays real-time FPS information:
```vue [app.vue]
```
::tip
TresLeches automatically displays an FPS graph overlay when you use the `fpsgraph` control. This provides real-time performance insights without manual implementation. Learn more at [TresLeches Documentation](https://tresleches.tresjs.org/misc/#fpsgraph){rel=""nofollow""}.
::
## Key Takeaways
::card-group
:::card{icon="i-lucide-target" title="Template Refs First"}
Use template refs for direct Three.js instance access in render loops to avoid reactivity overhead.
:::
:::card{icon="i-lucide-layers-2" title="Shallow Reactivity"}
Use `shallowRef` and `shallowReactive` when you need some reactivity without deep proxy overhead.
:::
:::card{icon="i-lucide-git-branch" title="Separate Concerns"}
Keep UI state reactive and animation state non-reactive for optimal performance.
:::
:::card{icon="i-lucide-activity" title="Monitor Performance"}
Use Nuxt DevTools and `@tresjs/leches` performance monitoring to identify reactivity bottlenecks in your 3D scenes.
:::
::
::tip
Remember: Vue's reactivity is powerful for UI updates but can be expensive in high-frequency render loops. Choose the right tool for each use case - reactive for user interactions, template refs for animations.
::
By understanding and applying these reactivity patterns, you can create performant 3D experiences that leverage Vue's strengths while avoiding common performance pitfalls.
# Constructor Arguments
## Understanding Constructor Arguments
Many Three.js classes require arguments when instantiated. For example, creating a `PerspectiveCamera` in vanilla Three.js:
```js
import { PerspectiveCamera } from 'three'
// Constructor: PerspectiveCamera(fov, aspect, near, far)
const camera = new PerspectiveCamera(45, 1, 0.1, 1000)
```
TresJS provides the `args` prop to pass these constructor arguments as an array.
## The `args` Prop
The `args` prop accepts an **array of constructor arguments** that are passed directly to the Three.js constructor in order:
```vue
```
::tip
The order of arguments in the array must match the Three.js constructor signature. Check the [Three.js documentation](https://threejs.org/docs/){rel=""nofollow""} for each class's constructor parameters.
::
## Common Examples
### Geometries
Geometries often require dimensional arguments:
```vue
```
### Cameras
```vue
```
### Lights
```vue
```
### Materials with Options Object
Some constructors accept an options object as the first argument:
```vue
```
::warning
When passing an options object, remember it still needs to be wrapped in an array since `args` always expects an array.
::
## When to Use `args` vs Props
TresJS allows setting many properties declaratively via props. Use this rule of thumb:
| Use `args` when... | Use props when... |
| -------------------------------------- | ------------------------------------- |
| Value is required at construction time | Value can be set after construction |
| Property is immutable after creation | Property is mutable |
| Creating geometries with dimensions | Setting position, rotation, scale |
| Setting material options at creation | Changing colors or values dynamically |
```vue
```
## Reactive Args
The `args` prop is reactive. When `args` changes, TresJS will **recreate the Three.js instance** with the new constructor arguments:
```vue
```
::warning
Recreating instances can be expensive. For frequently changing values, prefer using props or template refs instead of reactive `args`.
::
## Key Takeaways
::card-group
:::card{icon="i-lucide-list" title="Array Format"}
`args` always accepts an array, even for single arguments or options objects.
:::
:::card{icon="i-lucide-arrow-right" title="Order Matters"}
Arguments must match the Three.js constructor signature exactly.
:::
:::card{icon="i-lucide-refresh-cw" title="Reactivity"}
Changing `args` recreates the instance - use sparingly for performance.
:::
:::card{icon="i-lucide-book-open" title="Check the Docs"}
Reference [Three.js documentation](https://threejs.org/docs/){rel=""nofollow""} for constructor signatures.
:::
::
# Declarative Properties
## From Imperative to Declarative
In vanilla Three.js, you set properties imperatively:
```js
const mesh = new THREE.Mesh(geometry, material)
mesh.position.set(1, 2, 3)
mesh.rotation.set(0, Math.PI, 0)
mesh.visible = true
mesh.castShadow = true
```
TresJS lets you declare these properties directly on components:
```vue
```
:video-accordion{start-time="445" title="Watch this video from Alvarosabu about the TresJS core principles" video-id="XsXfF9-qe60"}
## Property Mapping Rules
TresJS automatically maps props to Three.js properties using these conventions:
### 1. Direct Properties
Props map directly to properties of the same name:
```vue
```
### 2. Properties with `.set()` Methods
When a property has a `.set()` method (like `Vector3`, `Euler`, `Color`), TresJS automatically calls it with array values:
```vue
```
### 3. Scalar Shorthand
For uniform scaling, pass a single number:
```vue
```
### 4. kebab-case Conversion
Vue's kebab-case props are converted to camelCase properties:
```vue
```
## Pierced Props (Nested Properties)
TresJS supports setting nested properties using dash notation:
### Transform Axes
```vue
```
### Color Channels
```vue
```
### Deep Nesting
```vue
```
## Special Props
### `lookAt`
The `lookAt` prop calls the object's `.lookAt()` method:
```vue
```
### `attach`
Controls how children attach to parents (used for geometries and materials):
```vue
```
::tip
Geometries and materials auto-attach - you rarely need to specify `attach` manually.
::
## Reactivity
All props are reactive by default. When a prop value changes, TresJS updates the Three.js property:
```vue
```
::warning
For high-frequency updates (animations at 60fps), use template refs instead of reactive props. See [Reactivity](https://docs.tresjs.org/essentials/concepts/reactivity) for performance patterns.
::
## Common Patterns
### Complete Mesh Example
```vue
```
### Light with Shadows
```vue
```
## Key Takeaways
::card-group
:::card{icon="i-lucide-move-3d" title="Array for Vectors"}
Use arrays for position, rotation, scale - TresJS calls `.set()` automatically.
:::
:::card{icon="i-lucide-git-branch" title="Pierced Props"}
Access nested properties with dash notation: `:position-x`, `:shadow-camera-near`.
:::
:::card{icon="i-lucide-repeat" title="Auto Conversion"}
kebab-case props convert to camelCase Three.js properties automatically.
:::
:::card{icon="i-lucide-refresh-cw" title="Reactive by Default"}
Props update Three.js properties reactively - be mindful of performance.
:::
::
# Extending the Catalogue
## The TresJS Catalogue
TresJS automatically generates Vue components for all classes exported from the `three` package. This **catalogue** maps component names to Three.js constructors:
| Component | Three.js Class |
| --------------------------- | ------------------------- |
| `` | `THREE.Mesh` |
| `` | `THREE.BoxGeometry` |
| `` | `THREE.PerspectiveCamera` |
But Three.js has many useful classes in `three/addons/` that aren't included by default, plus third-party libraries you might want to use.
## The `extend` Function
The `extend` function adds new classes to the catalogue, making them available as Tres components:
```ts
import { extend } from '@tresjs/core'
import { TextGeometry } from 'three/addons/geometries/TextGeometry'
// Add to catalogue
extend({ TextGeometry })
// Now available as
```
## Common Extensions
### Controls
```vue
```
::note
Controls such as Three.js' `OrbitControls` require the active camera and renderer DOM element as constructor arguments. Prefer the `@tresjs/cientos` component shown above, which reads both from the canvas context. If you extend a control class directly, wrap it in a component rendered inside `` and pass those values through the [`args` prop](https://docs.tresjs.org/essentials/concepts/constructor-arguments).
::
To extend the Three.js class directly, create a child component such as `ExtendedOrbitControls.vue`:
```vue
```
Render that component as a child of `` so `useTres()` can access the canvas context.
### Geometries
```vue
```
### Loaders
```vue
```
### Post-Processing
```vue
```
::tip
For post-processing, consider using [@tresjs/post-processing](https://github.com/Tresjs/post-processing){rel=""nofollow""} which provides ready-to-use effect components.
::
## Third-Party Libraries
### Using drei-style Libraries
```vue
```
### Custom Classes
You can extend with your own Three.js classes:
```vue
```
## Naming Convention
The component name is derived from the class name you provide to `extend`:
```ts
extend({ RoundedBoxGeometry }) //
extend({ TextGeometry }) //
extend({ MyCustomMaterial }) //
```
You can also use custom names:
```ts
extend({
CustomGeometry: RoundedBoxGeometry //
})
```
## Global vs Local Extension
### Global Extension (Recommended for Shared Classes)
Extend in your main app entry or a plugin:
```ts
// plugins/tres.ts or main.ts
import { extend } from '@tresjs/core'
import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry'
import { TextGeometry } from 'three/addons/geometries/TextGeometry'
extend({ RoundedBoxGeometry, TextGeometry })
```
### Local Extension (Component-Specific)
Extend in the component that needs it:
```vue
```
## TypeScript Support
For type safety with extended components, declare the types:
```ts
// types/tres.d.ts
import type { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry'
declare module '@tresjs/core' {
interface TresObjectMap {
RoundedBoxGeometry: typeof RoundedBoxGeometry
}
}
```
## Complete Example
```vue
```
## Key Takeaways
::card-group
:::card{icon="i-lucide-plus-circle" title="extend() Function"}
Use `extend()` to add any Three.js class to the TresJS catalogue.
:::
:::card{icon="i-lucide-package" title="Addons Support"}
Classes from `three/addons/` aren't included by default - extend them as needed.
:::
:::card{icon="i-lucide-code" title="Custom Classes"}
Your own Three.js classes can be extended and used as Vue components.
:::
:::card{icon="i-lucide-tag" title="Tres Prefix"}
Extended classes become available with the `Tres` prefix: `TextGeometry` β ``.
:::
::
# Child Attachments
## Not Vue Slots
When you nest components in TresJS:
```vue
```
This looks like Vue slots, but it isn't. TresJS uses a **custom Vue renderer** β there is no DOM, no ``. Instead, the renderer intercepts each child component and **attaches it as a property** of the parent Three.js object.
The equivalent vanilla Three.js code is:
```js
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial({ color: 'red' })
const mesh = new THREE.Mesh(geometry, material)
```
::tip
You cannot use `v-slot`, named slots, or scoped slots with TresJS scene components. Child attachment is handled at the renderer level, not Vue's component system.
::
:video-accordion{start-time="619" title="Watch this video from Alvarosabu about child attachments in TresJS" video-id="XsXfF9-qe60"}
## How Auto-Attachment Works
TresJS inspects the type of each child and automatically decides how to attach it:
| Child type | Attachment |
| ------------------------- | ------------------------- |
| `BufferGeometry` subclass | `parent.geometry = child` |
| `Material` subclass | `parent.material = child` |
| `Object3D` subclass | `parent.add(child)` |
This means in most cases you don't need to think about it β just nest the component and TresJS does the right thing.
## The `attach` Prop
For cases where auto-attachment isn't enough, use the `attach` prop to explicitly specify the target property:
```vue
```
This is particularly useful with objects that don't follow the standard `Mesh` pattern:
```vue
```
Or for attaching to nested properties:
```vue
```
::tip
Geometries and materials auto-attach correctly in the vast majority of cases. You only need `attach` for unusual object types or custom property targets.
::
## Scene Graph Children
When a child is an `Object3D` subclass (meshes, lights, groups, cameras), TresJS calls `parent.add(child)` to insert it into the Three.js scene graph. This is how nesting translates to scene hierarchy:
```vue
```
Moving or rotating the `` affects all children, exactly as in Three.js.
## Multiple Materials
Some Three.js objects accept an array of materials. Pass multiple materials using `attach` with array index notation:
```vue
```
## Key Takeaways
::card-group
:::card{icon="i-lucide-x-circle" title="Not Vue Slots"}
Children are attached via the custom renderer, not Vue's slot system. `v-slot` and named slots don't apply.
:::
:::card{icon="i-lucide-zap" title="Auto-Attachment"}
Geometries attach as `.geometry`, materials as `.material`, Object3Ds via `.add()` β all automatically.
:::
:::card{icon="i-lucide-link" title="The `attach` Prop"}
Explicitly target any property when auto-attachment doesn't cover your use case.
:::
:::card{icon="i-lucide-git-branch" title="Scene Hierarchy"}
Nesting Object3D children mirrors the Three.js scene graph β transforms propagate down the tree.
:::
::
# Essentials
#
## Component Overview
`` creates the necessary Three.js environment and bridges the gap between Vue's reactivity system and Three.js's imperative rendering approach. It is responsible for:
- Creating and configuring the WebGL canvas element
- Setting up the Three.js scene, camera, and renderer
- Establishing the render loop
- Providing the shared context to all child components
- Handling user events through a comprehensive event system
- Managing memory and disposal of Three.js objects
## Usage
```vue [app.vue]
```
## Canvas Size
The `` component offers flexible sizing options to fit different layout requirements. Understanding how canvas sizing works is crucial for creating responsive 3D experiences.
### Default Behavior: Parent Element Size
By default, `` automatically adapts to its **parent element's dimensions**. This is the most common and recommended approach as it integrates seamlessly with your existing CSS layout.
```vue [parent-sized.vue]
```
### Full Window Size
For immersive full-screen 3D experiences, use the `window-size` prop to make the canvas fill the entire browser viewport:
```vue [fullscreen.vue]
```
## API
::warning
**Not all props are reactive!** Some props are WebGL context options that are passed to the renderer constructor and **cannot be changed** after the canvas is created. Changing these props would require recreating the entire renderer and canvas context.
For detailed technical information about prop reactivity, see [GitHub Issue #982](https://github.com/Tresjs/tres/issues/982){rel=""nofollow""}.
::
### Props
::field-group
:::field{name="alpha" type="boolean"}
**π WebGL Context Option** - Controls the default clear alpha value. When set to `true`, the value is 0. Otherwise it's 1. Enables transparency in the canvas.
:::
:::field{name="antialias" type="boolean"}
**π WebGL Context Option** - Default: `true` - Whether to perform antialiasing. Improves visual quality by smoothing jagged edges.
:::
:::field{name="camera" type="TresCamera"}
Custom camera instance to use as main camera. If not provided, a default PerspectiveCamera will be created.
:::
:::field{name="clearAlpha" type="number"}
**β‘ Reactive** - Default: `1` - The alpha (transparency) value used when clearing the canvas. Range from 0 (transparent) to 1 (opaque).
:::
:::field{name="clearColor" type="string"}
**β‘ Reactive** - Default: `"#000000"` - The color the renderer will use to clear the canvas. Can be any valid CSS color string.
:::
:::field{name="depth" type="boolean"}
**π WebGL Context Option** - Whether the drawing buffer has a depth buffer of at least 16 bits. Required for depth testing and 3D rendering.
:::
:::field{name="dpr" type="number | [number, number]"}
**β‘ Reactive** - Device Pixel Ratio for the renderer. Can be a single number or a tuple defining a range [min, max]. Controls rendering resolution relative to device pixels.
:::
:::field{name="enableProvideBridge" type="boolean"}
Default: `true` - Whether to enable the provide/inject bridge between Vue and TresJS. When true, Vue's provide/inject will work across the TresJS boundary.
:::
:::field{name="failIfMajorPerformanceCaveat" type="boolean"}
**π WebGL Context Option** - Whether the renderer creation will fail upon low performance detection. See WebGL spec for details.
:::
:::field{name="fpsLimit" type="number"}
**β‘ Reactive** - Default: `undefined` (unlimited) - Caps the render loop frequency in FPS. Useful for reducing CPU/GPU usage or matching a target update rate.
:::
:::field{name="logarithmicDepthBuffer" type="boolean"}
**π WebGL Context Option** - Whether to use a logarithmic depth buffer. May be necessary for huge differences in scale. Can cause performance decrease.
:::
:::field{name="preserveDrawingBuffer" type="boolean"}
**π WebGL Context Option** - Whether to preserve the buffers until manually cleared or overwritten. Required for screenshots or canvas-to-image conversion.
:::
:::field{name="renderer" type="(ctx: TresRendererSetupContext) => TresRenderer"}
Custom [WebGL](https://threejs.org/docs/#api/en/renderers/WebGLRenderer){rel=""nofollow""} or experimental **WebGPU** renderer instance. Allows using a pre-configured renderer instead of creating a new one. Useful for advanced renderer customization.
::::note
To see how to use the WebGPU renderer, check the example here: **[WebGPU](https://docs.tresjs.org/api/advanced/web-gpu)**.
::::
:::
:::field{name="renderMode" type="'always' | 'on-demand' | 'manual'"}
Default: `"always"` - Controls when the scene renders:
- `always` - Renders every frame continuously
- `on-demand` - Renders only when changes are detected
- `manual` - Requires explicit render calls
:::
:::field{name="shadows" type="boolean"}
**β‘ Reactive** - Enable shadow mapping in the renderer. Required for casting and receiving shadows in your 3D scene.
:::
:::field{name="shadowMapType" type="ShadowMapType"}
**β‘ Reactive** - Default: `PCFShadowMap` on WebGL, `PCFSoftShadowMap` on WebGPU - The type of shadow map to use:
- `BasicShadowMap` - Basic shadow mapping (fastest, lowest quality)
- `PCFShadowMap` - Percentage-Closer Filtering shadows (good quality/performance balance)
- `PCFSoftShadowMap` - Deprecated on WebGL, three falls back to `PCFShadowMap` and logs a warning. Still supported on WebGPU.
- `VSMShadowMap` - Variance Shadow Maps (advanced technique)
:::
:::field{name="stencil" type="boolean"}
**π WebGL Context Option** - Whether the drawing buffer has a stencil buffer of at least 8 bits. Used for advanced rendering techniques.
:::
:::field{name="toneMapping" type="ToneMapping"}
**β‘ Reactive** - Default: `ACESFilmicToneMapping` - Defines the tone mapping algorithm used by the renderer:
- `NoToneMapping` - No tone mapping applied
- `LinearToneMapping` - Linear tone mapping
- `ReinhardToneMapping` - Reinhard tone mapping
- `CineonToneMapping` - Cineon tone mapping
- `ACESFilmicToneMapping` - ACES Filmic tone mapping (recommended)
- `CustomToneMapping` - Custom tone mapping
:::
:::field{name="toneMappingExposure" type="number"}
**β‘ Reactive** - Default: `1` - Exposure level of tone mapping. Controls the brightness/exposure of the rendered image.
:::
:::field{name="outputColorSpace" type="ColorSpace"}
**β‘ Reactive** - Color space for the output render. Controls how colors are displayed on screen.
:::
:::field{name="useLegacyLights" type="boolean"}
**π WebGL Context Option** - Whether to use the legacy lighting mode. When false, uses physically correct lighting calculations.
:::
:::field{name="windowSize" type="boolean"}
**β‘ Reactive** - Whether the canvas should be sized to the window. When true, canvas will be fixed positioned and full viewport size.
:::
:::field{name="customRendererOptions" type="TresCustomRendererOptions"}
Configuration options for the TresJS custom renderer:
- `primitivePrefix` - Custom prefix for the primitive component name (default: `""`). For example, setting this to `"my"` allows you to use `` instead of ``.
::::code-group
```vue [Custom Prefix]
```
```vue [Default (No Prefix)]
```
::::
:::
::
### Events
::field-group
:::field{name="ready" type="(context: TresContext) => void"}
Emitted when the TresJS context is fully initialized and ready to use. Provides access to the complete context object.
:::
:::field{name="render" type="(context: TresContext) => void"}
Emitted on every frame render. Useful for custom render logic or performance monitoring.
:::
:::field{name="beforeLoop" type="(context: TresContextWithClock) => void"}
Emitted before each render loop iteration. Includes clock information for time-based animations.
:::
:::field{name="loop" type="(context: TresContextWithClock) => void"}
Emitted during each render loop iteration. Perfect for custom animation logic.
:::
:::field{name="pointermissed" type="(event: PointerEvent) => void"}
Emitted when a pointer event doesn't hit any 3D objects in the scene. Useful for deselecting objects or closing menus.
:::
:::field{name="pointerover" type="(event: PointerEvent) => void"}
Emitted when the pointer moves over a 3D object. Supports event bubbling from child objects.
:::
:::field{name="pointerout" type="(event: PointerEvent) => void"}
Emitted when the pointer moves out of a 3D object. Supports event bubbling from child objects.
:::
:::field{name="pointerenter" type="(event: PointerEvent) => void"}
Emitted when the pointer enters a 3D object. Does not bubble from child objects.
:::
:::field{name="pointerleave" type="(event: PointerEvent) => void"}
Emitted when the pointer leaves a 3D object. Does not bubble from child objects.
:::
:::field{name="pointerdown" type="(event: PointerEvent) => void"}
Emitted when a pointer button is pressed down over a 3D object.
:::
:::field{name="pointerup" type="(event: PointerEvent) => void"}
Emitted when a pointer button is released over a 3D object.
:::
:::field{name="click" type="(event: PointerEvent) => void"}
Emitted when a 3D object is clicked. Equivalent to pointerdown followed by pointerup.
:::
::
### Exposed Properties
::field-group
:::field{name="context" type="TresContext | undefined"}
The complete TresJS context object containing scene, renderer, camera, and other core instances. Available after the component is mounted.
:::
:::field{name="dispose" type="() => void"}
Method to manually dispose of the WebGL context and clean up resources. Useful for cleanup when dynamically removing canvas instances.
:::
::
#
## Component Overview
`` is the internal component that powers ``. It mounts the TresJS renderer and scene using a **provided canvas element** instead of creating one for you.
::note
This component is exported for advanced use cases only. In most apps you should keep using ``.
::
## When to Use It
Use `` only if you already own the WebGL canvas (or must integrate with a host framework that provides one) and you still want TresJS to manage the Three.js scene, render loop, and events.
## Usage
```vue [app.vue]
```
## Notes
- You must pass a valid `canvas` element.
- Canvas sizing and styling are **your** responsibility.
- Props and events match ``, so you can reuse the same API surface.
# Tres Components
## The Autogenerated Catalogue
TresJS provides an **autogenerated catalogue** of Vue components that map directly to Three.js classes. Any class exported from the `three` package is automatically available as a Vue component with the `Tres` prefix.
### Naming Convention
| Vue Component | Three.js Class |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `` | [`new THREE.PerspectiveCamera()`](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera){rel=""nofollow""} |
| `` | [`new THREE.Mesh()`](https://threejs.org/docs/#api/en/objects/Mesh){rel=""nofollow""} |
| `` | [`new THREE.BoxGeometry()`](https://threejs.org/docs/#api/en/geometries/BoxGeometry){rel=""nofollow""} |
| `` | [`new THREE.MeshBasicMaterial()`](https://threejs.org/docs/#api/en/materials/MeshBasicMaterial){rel=""nofollow""} |
| `` | [`new THREE.AmbientLight()`](https://threejs.org/docs/#api/en/lights/AmbientLight){rel=""nofollow""} |
### No Imports Needed
Unlike vanilla Three.js where you must import each class:
```js
import { PerspectiveCamera, Mesh, BoxGeometry } from 'three'
```
With TresJS, components are available automatically:
```vue
```
:video-accordion{start-time="334" title="Watch this video from Alvarosabu about the TresJS core principles" video-id="XsXfF9-qe60"}
::tip
Since TresJS components map directly to Three.js classes, you can use the [Three.js documentation](https://threejs.org/docs/){rel=""nofollow""} as your API reference for constructor arguments and properties.
::
## Working with Tres Components
TresJS components accept two types of configuration:
### Constructor Arguments (`args`)
Pass arguments to the Three.js constructor using the `args` prop:
```vue
```
:read-more{title="Learn more about constructor arguments" to="https://docs.tresjs.org/essentials/concepts/constructor-arguments"}
### Declarative Properties
Set Three.js object properties using Vue props:
```vue
```
:read-more{title="Learn more about declarative properties" to="https://docs.tresjs.org/essentials/concepts/declarative-properties"}
### Child Attachments
Nesting components inside others is how you compose Three.js objects in TresJS. This is **not** Vue's slot system β TresJS's custom renderer intercepts children and attaches them as properties of the parent Three.js object:
```vue
```
`Object3D` children (meshes, lights, groups) are added via `.add()` and become part of the scene graph hierarchy.
:read-more{title="Learn more about child attachments" to="https://docs.tresjs.org/essentials/concepts/child-attachments"}
### Extending the Catalogue
Add Three.js addons or custom classes using `extend()`:
```vue {2-5,11}
```
:read-more{title="Learn more about extending the catalogue" to="https://docs.tresjs.org/essentials/concepts/extending-catalogue"}
# TresPortal
`` reparents its declarative children into a target `Object3D` (often a separate `Scene`) instead of the surrounding scene graph. It is a thin wrapper over Vue's built-in ``, so children remain fully reactive β adding, removing, or updating them works as usual.
It is the building block behind [`MeshPortalMaterial`](https://cientos.tresjs.org/api/materials/mesh-portal-material){rel=""nofollow""} in `@tresjs/cientos`.
:examples-portal
## Usage
```vue
```
## Props
| Prop | Type | Default | Description |
| ---------- | ---------- | ------- | -------------------------------------------------------- |
| `to` | `Object3D` | β | Target object/scene to reparent children into. Required. |
| `disabled` | `boolean` | `false` | When `true`, children render in place (main scene). |
::note
`` only handles **structural** reparenting β children are added to the target via `nodeOps`, and the target is also where `attach` resolves. So `` or `` inside a portal set the **target** scene's properties.
By itself it does **not** override the injected scene context: children's `useTres().scene` still returns the main scene, so imperative helpers like the cientos `` component would target the main scene. A consumer can add this by `provide()`-ing a context whose `scene` is the target β `provide`/`inject` follows the mounted tree, so it reaches the portal's slot children through the ``. `MeshPortalMaterial` does exactly this, which is why `` works inside it. Rendering the target scene (e.g. to a texture) is also the consumer's responsibility β `MeshPortalMaterial` does that via an FBO.
::
# TresJS API Reference
::card-group
:::card
---
spotlight: true
icon: i-lucide-box
title: Components
to: https://docs.tresjs.org/api/components/tres-canvas
---
Explore the TresJS components.
:::
:::card
---
spotlight: true
icon: i-lucide-arrow-right-left
title: Composables
to: https://docs.tresjs.org/api/composables/use-tres
---
Discover TresJS composables for context access, loading resources, animation and more.
:::
:::card
---
spotlight: true
icon: i-lucide-brain-circuit
title: Advanced
to: https://docs.tresjs.org/api/advanced/primitives
---
Go a step further with advanced topics like performance optimization and more.
:::
::
# useTres
The `useTres` composable provides convenient access to a simplified TresJS context with direct access to core properties like the scene, renderer, camera, and utility functions. It's designed to be more straightforward than `useTresContext` while still providing access to essential TresJS functionality.
## Usage
::warning
`useTres` can only be used in child components of a [`TresCanvas`](https://docs.tresjs.org/api/components/tres-canvas) component, as its data is provided by [`TresCanvas`](https://docs.tresjs.org/api/components/tres-canvas).
::
```ts
import { useTres } from '@tresjs/core'
const { scene, renderer, camera, sizes, invalidate, advance } = useTres()
// Access the active camera
console.log('Current camera:', camera.value)
// Get canvas dimensions
console.log('Canvas size:', sizes.width.value, sizes.height.value)
// Trigger a re-render in on-demand mode
invalidate()
```
### Basic Example
::code-group
```vue [MyComponent.vue]
```
```vue [App.vue]
```
::
### Extending the Catalogue
Use the `extend` function to add custom Three.js objects to the TresJS catalogue:
```vue
```
:read-more{to="https://docs.tresjs.org/api/components/tres-objects#extending-the-catalogue"}
### Manual Rendering Control
For precise control over when frames are rendered:
```vue
```
## Properties
The `useTres` composable returns an object with the following properties:
::field-group
:::field{name="scene" type="ShallowRef"}
The Three.js scene object containing all 3D objects in your scene.
:::
:::field{name="renderer" type="TresRenderer"}
The Three.js WebGL renderer instance. Direct access to renderer methods and properties.
:::
:::field{name="camera" type="ComputedRef"}
The currently active camera in the scene. Reactive reference that updates when the active camera changes.
:::
:::field{name="sizes" type="SizesType"}
Reactive size information including canvas width, height, aspect ratio, and pixel ratio.
:::
:::field{name="controls" type="Ref"}
Reference to the current camera controls instance (e.g., OrbitControls, FlyControls).
:::
:::field{name="extend" type="(objects: any) => void"}
Function to extend the TresJS component catalogue with custom Three.js objects.
:::
:::field{name="events" type="EventManager"}
The event manager instance for handling pointer interactions with 3D objects.
:::
:::field{name="invalidate" type="() => void"}
Function to mark the scene as needing an update in the next frame. Essential for on-demand rendering mode.
:::
:::field{name="advance" type="() => void"}
Function to manually advance the render loop by one frame. Required for manual rendering mode.
:::
::
## Practical Examples
### Responsive Camera Setup
```vue
```
### Custom Render Pipeline
```vue
```
### Dynamic Scene Management
```vue
```
## Difference from useTresContext
`useTres` provides a simplified interface compared to `useTresContext`:
| Feature | useTres | useTresContext |
| -------------- | ------------------------------ | ------------------------------------------ |
| **Renderer** | Direct `TresRenderer` instance | Renderer manager with additional methods |
| **Camera** | Active camera reference | Full camera management system |
| **Complexity** | Simplified, focused API | Complete context with all internal details |
| **Use Case** | Most common scenarios | Advanced use cases, internal operations |
::tip
**Developer Tip:**
Choose `useTres` for typical 3D scene interactions, and `useTresContext` when you need access to the complete internal context.
::
## Type
```ts [Signature]
function useTres(): TresPartialContext
interface TresPartialContext {
/** The Three.js scene object containing all 3D objects */
scene: ShallowRef
/** The Three.js WebGL renderer instance */
renderer: TresRenderer
/** The currently active camera */
camera: ComputedRef
/** Reactive size information for the canvas */
sizes: SizesType
/** Reference to current camera controls */
controls: Ref
/** TresJS extension function for adding custom objects */
extend: (objects: any) => void
/** Event manager for pointer interactions */
events: EventManager
/** Mark scene for re-render in on-demand mode */
invalidate: () => void
/** Manually advance one frame in manual mode */
advance: () => void
}
interface SizesType {
/** Canvas width in pixels */
width: Ref
/** Canvas height in pixels */
height: Ref
/** Canvas aspect ratio (width / height) */
aspectRatio: Ref
/** Device pixel ratio */
pixelRatio: Ref
}
type TresRenderer = WebGLRenderer | Renderer
type TresControl = any // Camera controls instance
type EventManager = any // Event manager instance
```
# useTresContext
The `useTresContext` composable provides complete access to the full TresJS context with all internal management systems, including advanced camera management, renderer control, and event handling. It's designed for advanced use cases where you need direct access to all TresJS internals.
## Usage
::warning
`useTresContext` can only be used in child components of a [`TresCanvas`](https://docs.tresjs.org/api/components/tres-canvas) component, as its data is provided by [`TresCanvas`](https://docs.tresjs.org/api/components/tres-canvas).
::
```ts
import { useTresContext } from '@tresjs/core'
const { scene, renderer, camera, sizes, events } = useTresContext()
// Access the camera management system
console.log('Active camera:', camera.activeCamera.value)
console.log('All cameras:', camera.cameras.value)
// Access the complete renderer manager
console.log('Render mode:', renderer.mode)
console.log('Can invalidate:', renderer.canBeInvalidated.value)
// Register a new camera
const myCamera = new PerspectiveCamera(75, 1, 0.1, 1000)
camera.registerCamera(myCamera, true) // Set as active
```
### Advanced Camera Management
::code-group
```vue [CameraController.vue]
```
```vue [App.vue]
```
::
### Advanced Renderer Control
Use the full renderer manager for sophisticated rendering control:
```vue
```
### Event System Integration
Access the complete event management system:
```vue
```
### Custom Renderer Setup
For complete control over renderer initialization:
```vue
```
## Properties
The `useTresContext` composable returns the complete TresJS context with the following properties:
::field-group
:::field{name="scene" type="ShallowRef"}
The Three.js scene object containing all 3D objects in your scene.
:::
:::field{name="sizes" type="SizesType"}
Reactive size information including canvas width, height, aspect ratio, and pixel ratio.
:::
:::field{name="extend" type="(objects: any) => void"}
Function to extend the TresJS component catalogue with custom Three.js objects.
:::
:::field{name="camera" type="UseCameraReturn"}
Complete camera management system with registration, deregistration, and active camera control.
:::
:::field{name="controls" type="Ref"}
Reference to the current camera controls instance (e.g., OrbitControls, FlyControls).
:::
:::field{name="renderer" type="UseRendererManagerReturn"}
Complete renderer management system with advanced rendering control, frame management, and event hooks.
:::
:::field{name="events" type="EventManager"}
Complete event management system for handling pointer interactions and global events.
:::
::
### Camera Management System (`camera`)
::field-group
:::field{name="activeCamera" type="ComputedRef"}
The currently active camera in the scene. Always guaranteed to exist.
:::
:::field{name="cameras" type="Ref"}
Array of all registered cameras in the scene.
:::
:::field
---
name: registerCamera
type: "(camera: TresCamera, active?: boolean) => void"
---
Register a new camera in the scene. Optionally set it as the active camera.
:::
:::field{name="deregisterCamera" type="(camera: TresCamera) => void"}
Remove a camera from the scene management system.
:::
:::field
---
name: setActiveCamera
type: "(cameraOrUuid: string | TresCamera) => void"
---
Set a specific camera as the active one using camera instance or UUID.
:::
::
### Renderer Management System (`renderer`)
::field-group
:::field{name="instance" type="TresRenderer"}
The raw Three.js WebGL renderer instance.
:::
:::field{name="loop" type="RafLoop"}
The render loop system with start, stop, and event hooks.
:::
:::field{name="advance" type="() => void"}
Manually advance one frame in manual render mode.
:::
:::field{name="invalidate" type="(frames?: number) => void"}
Mark the scene for re-rendering in on-demand mode. Optional frame count parameter.
:::
:::field{name="canBeInvalidated" type="ComputedRef"}
Whether the renderer can currently be invalidated (on-demand mode only).
:::
:::field{name="mode" type="RenderMode"}
Current render mode: 'always', 'on-demand', or 'manual'.
:::
:::field
---
name: onReady
type: "(callback: (renderer: TresRenderer) => void) => void"
---
Register a callback for when the renderer is fully initialized.
:::
:::field
---
name: onRender
type: "(callback: (renderer: TresRenderer) => void) => void"
---
Register a callback that fires after each frame is rendered.
:::
:::field{name="replaceRenderFunction" type="(fn: RenderFunction) => void"}
Take complete control over the rendering process with a custom render function.
:::
::
## Difference from useTres
`useTresContext` provides complete access to all TresJS internals compared to the simplified [`useTres`](https://docs.tresjs.org/api/composables/use-tres) interface.
| Feature | useTresContext | useTres |
| ----------------------- | ----------------------------------------------------------- | --------------------------------- |
| **Renderer** | Complete renderer manager with advanced controls | Direct renderer instance only |
| **Camera** | Full camera management system with registration/switching | Active camera reference only |
| **Complexity** | Complete internal access, all management features | Simplified, focused API |
| **Use Case** | Advanced scenarios, plugin development, internal operations | Most common 3D scene interactions |
| **Performance Control** | Fine-grained frame control, custom render functions | Basic invalidate/advance only |
| **Event Handling** | Complete event management system | Basic event manager access |
::tip
**Developer Tip:**
Choose `useTresContext` for advanced 3D applications, plugin development, or when you need complete control over the rendering pipeline. Use [`useTres`](https://docs.tresjs.org/api/composables/use-tres) for typical 3D scene interactions.
::
## Type
```ts [Signature]
function useTresContext(): TresContext
interface TresContext {
/** The Three.js scene object containing all 3D objects */
scene: ShallowRef
/** Reactive size information for the canvas */
sizes: SizesType
/** TresJS extension function for adding custom objects */
extend: (objects: any) => void
/** Complete camera management system */
camera: UseCameraReturn
/** Reference to current camera controls */
controls: Ref
/** Complete renderer management system */
renderer: UseRendererManagerReturn
/** Complete event management system */
events: EventManager
}
interface UseCameraReturn {
/** The currently active camera */
activeCamera: ComputedRef
/** Array of all registered cameras */
cameras: Ref
/** Register a new camera */
registerCamera: (camera: TresCamera, active?: boolean) => void
/** Remove a camera from management */
deregisterCamera: (camera: TresCamera) => void
/** Set a specific camera as active */
setActiveCamera: (cameraOrUuid: string | TresCamera) => void
}
interface UseRendererManagerReturn {
/** The render loop system */
loop: RafLoop
/** The raw Three.js renderer instance */
instance: TresRenderer
/** Manually advance one frame (manual mode) */
advance: () => void
/** Register callback for renderer ready event */
onReady: (callback: (renderer: TresRenderer) => void) => void
/** Register callback for after render event */
onRender: (callback: (renderer: TresRenderer) => void) => void
/** Mark scene for re-render (on-demand mode) */
invalidate: (frames?: number) => void
/** Whether renderer can be invalidated */
canBeInvalidated: ComputedRef
/** Current render mode */
mode: RenderMode
/** Take control of render function */
replaceRenderFunction: (fn: RenderFunction) => void
}
interface SizesType {
/** Canvas width in pixels */
width: Ref
/** Canvas height in pixels */
height: Ref
/** Canvas aspect ratio (width / height) */
aspectRatio: Ref
/** Device pixel ratio */
pixelRatio: Ref
}
type RenderMode = 'always' | 'on-demand' | 'manual'
type RenderFunction = (notifySuccess: () => void) => void
type TresRenderer = WebGLRenderer | Renderer
type TresCamera = Camera
```
# useLoop
The `useLoop` composable allows you to register callbacks that run before and after each render cycle, or take complete control of the rendering process within `TresCanvas` components.
::dotted-diagram
:diagrams-render-loop
::
## Usage
::warning
`useLoop` can only be used in child components of a [`TresCanvas`](https://docs.tresjs.org/api/components/tres-canvas) component, as its data is provided by [`TresCanvas`](https://docs.tresjs.org/api/components/tres-canvas).
::
```ts
import { useLoop } from '@tresjs/core'
const { onBeforeRender, onRender } = useLoop()
onBeforeRender(() => {
console.log('before render')
})
onRender(() => {
console.log('after render')
})
```
### Priority
The `onBeforeRender` and `onRender` callbacks can be registered with a priority. The priority is a number that determines the order in which the callbacks are executed. The default priority is 0.
::code-group
```ts [onBeforeRender]
onBeforeRender(() => {
console.log('earlier before render')
}, -10)
onBeforeRender(() => {
console.log('just before render')
})
onBeforeRender(() => {
console.log('even closer before render')
}, 10)
```
```ts [onRender]
onRender(() => {
console.log('even closer after render')
}, -10)
onRender(() => {
console.log('just after render')
})
onRender(() => {
console.log('later after render')
}, 10)
```
::
### Register update callbacks
The most common use of `onBeforeRender` is to register update callbacks for animations, such as rotating or moving objects in the scene.
::code-group
```vue [AnimatedCube.vue]
```
```vue [App.vue]
```
::
### Take Over the Render Loop
You can take complete control of the rendering process by using the `render` method from `useLoop`. This allows you to implement custom rendering logic, post-processing effects, or conditional rendering.
```ts
import { useLoop, useTresContext } from '@tresjs/core'
const { render } = useLoop()
const { renderer, scene, camera } = useTresContext()
// Take over the render loop with custom logic
render((notifySuccess) => {
// Your custom rendering logic here
if (camera.activeCamera.value) {
renderer.instance.render(scene.value, camera.activeCamera.value)
// IMPORTANT: Call notifySuccess() to indicate the frame was rendered successfully
notifySuccess()
}
})
```
::warning
**Success Callback Required**: You must call the provided callback (named `notifySuccess()` in the example above) to properly notify the render loop that the frame was completed. This is essential for the render modes (`always`, `on-demand`, `manual`) to function correctly.
::
#### Custom Rendering Examples
Here are examples showing different custom rendering scenarios:
::code-group
```vue [Conditional Rendering]
```
```vue [Post-processing]
```
```vue [Multi-pass Rendering]
```
::
::warning
When you take over the render loop, you become responsible for:
- Manually triggering a render
- **Always calling `notifySuccess()` at the end of your render function**
- Handling conditional rendering logic yourself
- Managing any post-processing effects
- Ensuring proper frame timing and performance
The built-in render modes (`always`, `on-demand`, `manual`) will be bypassed when using custom rendering.
::
## Callback Parameters
Both `onBeforeRender` and `onRender` callbacks receive a context object containing timing information and access to the TresJS context:
```ts
onBeforeRender(({ delta, elapsed, renderer, camera, scene, sizes, invalidate, advance }) => {
// Timing information
console.log('Time since last frame:', delta) // in seconds
console.log('Total elapsed time:', elapsed) // in seconds
// TresJS context access
console.log('Current camera:', camera.value)
console.log('Scene:', scene.value)
console.log('Canvas size:', sizes.width.value, sizes.height.value)
// Control methods
invalidate() // Mark scene for re-render (useful in on-demand mode)
advance() // Manually advance one frame (useful in manual mode)
})
```
### `onBeforeRender` and `onRender` Parameters
::field-group
:::field{name="delta" type="number"}
Time in seconds since the last frame. Perfect for frame-rate independent animations.
:::
:::field{name="elapsed" type="number"}
Total elapsed time in seconds since the render loop started. Useful for time-based effects.
:::
:::field{name="renderer" type="TresRenderer"}
The Three.js WebGL renderer instance. Access to all renderer methods and properties.
:::
:::field{name="camera" type="ComputedRef"}
The currently active camera in the scene. Reactive reference that updates when camera changes.
:::
:::field{name="scene" type="ShallowRef"}
The Three.js scene object containing all 3D objects.
:::
:::field{name="sizes" type="SizesType"}
Reactive size information including width, height, aspect ratio and pixel ratio of the canvas.
:::
:::field{name="invalidate" type="() => void"}
Function to mark the scene as needing an update in the next frame. Particularly useful in on-demand rendering mode.
:::
:::field{name="advance" type="() => void"}
Function to manually advance the render loop by one frame. Especially useful in manual rendering mode.
:::
:::field{name="controls" type="Ref"}
Reference to the current camera controls (if any). Useful for camera-based animations.
:::
:::field{name="events" type="EventManager"}
The event manager instance for handling pointer interactions with 3D objects.
:::
::
### The `render` Method Parameters
The `render` method takes a function that receives a single `notifySuccess` callback parameter:
::field-group
:::field{name="notifySuccess" type="() => void"}
A callback function that must be called to indicate the frame has been successfully rendered. This is essential for the render loop to function correctly across all render modes.
:::
::
::note
**Important**: The `render` method does NOT receive a context object like `onBeforeRender` and `onRender`. Instead, use `useTres()` to access the renderer, scene, and camera within your render function.
::
## Type
```ts [Signature]
function useLoop(): UseLoopReturn
interface UseLoopReturn {
/** Stops the render loop */
stop: () => void
/** Starts the render loop */
start: () => void
/** Reactive reference indicating if the loop is currently active */
isActive: Ref
/** Register a callback to run before each render */
onBeforeRender: (fn: LoopCallback, priority?: number) => { off: () => void }
/** Register a callback to run after each render */
onRender: (fn: LoopCallback, priority?: number) => { off: () => void }
/** Take complete control over the rendering process */
render: (fn: RenderFunction) => void
}
type LoopCallback = (context: LoopContext) => void | Promise
type RenderFunction = (notifySuccess: () => void) => void
interface LoopContext {
/** Time in seconds since the last frame */
delta: number
/** Total elapsed time in seconds since render loop started */
elapsed: number
/** The Three.js WebGL renderer instance */
renderer: TresRenderer
/** The currently active camera */
camera: ComputedRef
/** The Three.js scene object */
scene: ShallowRef
/** Reactive size information for the canvas */
sizes: SizesType
/** Reference to current camera controls */
controls: Ref
/** TresJS extension function */
extend: (objects: any) => void
/** Event manager for pointer interactions */
events: EventManager
/** Mark scene for re-render in on-demand mode */
invalidate: () => void
/** Manually advance one frame in manual mode */
advance: () => void
}
interface SizesType {
/** Canvas width in pixels */
width: Ref
/** Canvas height in pixels */
height: Ref
/** Canvas aspect ratio (width / height) */
aspectRatio: Ref
/** Device pixel ratio */
pixelRatio: Ref
}
type TresRenderer = WebGLRenderer | Renderer
```
# useGraph
The `useGraph` composable provides a convenient way to extract and reactively access all named nodes, materials, and meshes from a Three.js object or scene. This is especially useful when working with loaded models or complex object hierarchies, allowing you to reference and manipulate specific parts of your 3D scene by name.
## Usage
```ts
import { useGraph } from '@tresjs/core'
import { BoxGeometry, Group, Mesh, MeshStandardMaterial } from 'three'
// Create a group and add a mesh with a named material
const group = new Group()
const box = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshStandardMaterial({ name: 'FancyMaterial', color: 'red' })
)
box.name = 'Box'
group.add(box)
// Use useGraph to extract nodes and materials
const { nodes, materials } = useGraph(group)
// Change the position of the box by name
nodes.Box.position.set(1, 0, 0)
// Change the color of the material by name
materials.FancyMaterial.color.set('blue')
```
::tip
`useGraph` is especially useful for working with loaded GLTF/FBX models, where you want to access specific meshes or materials by their names as defined in the 3D modeling tool.
::
### Example
When loading a GLTF model, you can use `useGraph` to easily access and manipulate specific parts of the model:
```vue [Model.vue]
```
::note
**Best Practice:** Always assign unique names to important nodes and materials in your 3D models for easier access in code.
::
## API
The `useGraph` composable returns a computed ref containing a `TresObjectMap` with the following structure:
::field-group
:::field{name="nodes" type="Record"}
All named nodes in the object hierarchy, indexed by their `name` property.
:::
:::field{name="materials" type="Record"}
All unique materials, indexed by their `name` property. Only the first material with a given name is included.
:::
:::field{name="meshes" type="Record"}
All unique meshes, indexed by their `name` property. Only the first mesh with a given name is included.
:::
:::field{name="scene" type="Scene | undefined"}
The root scene object, if available.
:::
::
### Type Signature
```ts [Signature]
function useGraph(object: MaybeRef): ComputedRef
interface TresObjectMap {
nodes: { [name: string]: TresObject }
materials: { [name: string]: TresMaterial }
meshes: { [name: string]: Mesh }
scene?: Scene
}
```
## Edge Cases & Notes
- Only the first material or mesh with a given name is included in the map. Duplicate names are ignored after the first occurrence.
- Unnamed objects are not included in the `nodes` map.
- If the input object is `null` or `undefined`, all maps will be empty.
# useLoader
The `useLoader` composable provides a reactive and easy-to-use method for loading 3D models and textures with any Three.js loader. It supports progress tracking, error handling, and works seamlessly with Vue's reactivity system. This makes it ideal for loading assets in TresJS scenes, including GLTF, FBX, textures, and more.
## Usage
### Loading a Texture and Applying to a Mesh
:examples-use-loader-texture
```vue [TextureExample.vue]
```
### Loading a GLTF Model and Rendering a Named Node
:examples-use-loader-gltf
```vue [GLTFExample.vue]
```
### Loading an FBX Model and Rendering It
```vue [FBXExample.vue]
```
## API
The `useLoader` composable returns a reactive object with the following properties:
::field-group
:::field{name="state" type="Ref"}
The loaded asset (model, texture, etc.), or `null` if not loaded yet.
:::
:::field{name="isLoading" type="Ref"}
Indicates if the asset is currently loading.
:::
:::field{name="error" type="Ref"}
Any error encountered during loading.
:::
:::field
---
name: progress
type: "{ loaded: number; total: number; percentage: number }"
---
Progress information for the current load operation.
:::
:::field{name="load" type="(path: string) => void"}
Method to load a new asset from a different path.
:::
::
### Type Signature
```ts [Signature]
function useLoader(
Loader: LoaderProto,
path: MaybeRef,
options?: TresLoaderOptions,
): UseLoaderReturn
```
## Tips & Best Practices
- **Always use the correct loader for your asset type** (e.g., `GLTFLoader` for `.glb/.gltf`, `FBXLoader` for `.fbx`, `TextureLoader` for images).
- **Track loading progress** using the `progress` object to show user feedback.
- **Use a `LoadingManager`** for global progress tracking across multiple assets.
- **Handle errors** by watching the `error` ref and providing fallback UI.
- **Reactive paths:** You can pass a `ref` as the path to automatically reload when the path changes.
::note
If you need to load multiple assets at once, create multiple `useLoader` instances or use a `LoadingManager` to coordinate progress.
::
# Pointer Events
TresJS provides a comprehensive pointer events system that allows you to interact with 3D objects using mouse, touch, and other pointer devices. The event system is built on top of the powerful [`@pmndrs/pointer-events`](https://www.npmjs.com/package/@pmndrs/pointer-events){rel=""nofollow""} package, providing framework-agnostic pointer event handling for Three.js objects.
## Basic Usage
Pointer events are automatically enabled in `TresCanvas` and work seamlessly with all 3D objects. Simply add event listeners directly to your TresJS components:
:examples-pointer-events
```vue
```
## Available Events
TresJS supports all standard pointer events that you can listen to on any 3D object:
### Mouse Events
- `@click` - Fired when the object is clicked
- `@doubleclick` - Fired when the object is double-clicked
- `@contextmenu` - Fired when right-clicking the object
- `@pointerdown` - Fired when pointer is pressed down on the object
- `@pointerup` - Fired when pointer is released over the object
### Hover Events
- `@pointerenter` - Fired when pointer enters the object's bounds
- `@pointerleave` - Fired when pointer leaves the object's bounds
- `@pointerover` - Fired when pointer is over the object
- `@pointerout` - Fired when pointer moves away from the object
- `@pointermove` - Fired when pointer moves while over the object
### Drag Events
- `@pointercancel` - Fired when pointer interaction is cancelled
## Event Objects
Event handlers receive a `PointerEvent` object with useful information:
```vue
```
## Pointer Missed Events
You can listen for events when the pointer misses all objects (clicks on empty space) by adding the `@pointermissed` event directly to the `TresCanvas` component:
```vue
```
## Event Propagation
Events bubble up through the 3D object hierarchy. You can stop propagation using the standard event methods:
:examples-pointer-events-propagation
```vue
```
## Performance Considerations
- Events are automatically optimized using raycasting
- Only objects with event listeners are tested for intersections
- Use `pointer-events: none` in CSS to disable interaction on specific objects
- Consider using object pooling for scenes with many interactive objects
## TypeScript Support
TresJS provides full TypeScript support for pointer events:
```ts
import type { PointerEvent } from '@pmndrs/pointer-events'
function handlePointerEvent(event: PointerEvent) {
// Full type safety for event properties
console.log(event.point) // Vector3
console.log(event.object) // Object3D
console.log(event.xy) // [number, number]
}
```
# Type Guards
To help you work with Three.js objects more effectively, TresJS provides a set of type guard methods. These methods allow you to determine the type of a Three.js object, making your code more robust and easier to maintain.
The supported type guards are:
- `isBufferGeometry`
- `isCamera`
- `isColor`
- `isColorRepresentation`
- `isFog`
- `isGroup`
- `isLayers`
- `isLight`
- `isMaterial`
- `isMesh`
- `isObject3D`
- `isOrthographicCamera`
- `isPerspectiveCamera`
- `isScene`
# v-log
With the v-log directive provided by **TresJS**, you can do this by just adding `v-log` to the instance.
```vue {2,11}
```
## Arguments
Note that you can pass a modifier with the name of **any property**, for example:
```html
```
::prose-warning
The component `` will not log the canvas or the scene.
::
# Scaling Performance π
Running WebGL in the browser can be resource-intensive depending on the user's device capabilities. To make 3D accessible to everyone, it's important to optimize your applications for performance, especially on low-end devices. This guide shares practical tips to help you get the best performance from your TresJS projects.
## Rendering Modes
:examples-on-demand
By default, TresJS renders your scene on every frame. While this works for most cases, if you're building a game or a complex app, you may want to control when rendering happens.
Otherwise it might drain your device battery π and make your computer sound like an airplane π«.
To optimize performance, **scene rendering should be triggered only when necessary events** occurβlike user input, camera transformations, or object animations.
You can achieve this by setting the `renderMode` prop to `on-demand` or `manual`:
### Mode `on-demand`
```vue [on-demand.vue]
```
#### Automatic Invalidation
When using `render-mode="on-demand"`, TresJS will automatically invalidate the current frame by observing component props and lifecycle hooks like `onMounted` and `onUnmounted`. It will also invalidate the frame when resizing the window or changing any prop from the `` component like `clearColor` or `antialias`.
### Mode `manual`
If you want to have full control of when the scene is rendered, you can set the `render-mode` prop to `manual`:
```vue [manual-mode.vue]
```
In this mode, Tres will not render the scene automatically. You will need to call the `advance()` method from the useTres composable to render the scene:
```vue [manual-invalidate.vue]
```
#### Manual Invalidation
Itβs often not possible to observe every change in your application. In such cases, you can manually invalidate the frame using the `invalidate()` method from the useTres composable.:
```vue [manual-invalidate.vue]
```
### Mode `always`
In this rendering mode, Tres will continuously render the scene on every frame. This is the default mode and the easiest to use, but it's also the most resource expensive one.
```vue [always-mode.vue]
```
::note
**Tip:** Use `on-demand` or `manual` rendering modes for static or mostly-static scenes to save resources and improve battery life.
::
## Limit FPS with `fpsLimit`
If your scene does not need to update at full refresh rate, cap the loop with the `fps-limit` prop on `TresCanvas`. Lowering FPS reduces CPU/GPU work and can improve thermals and battery life.
```vue [fps-limit.vue]
```
## Reactivity and Performance
Vue's reactivity system is powerful, but when working with Three.js objects in a real-time 3D scene, it can introduce unnecessary overhead. Since TresJS scenes often update at high frame rates (e.g., 60 FPS), making Three.js objects deeply reactive can significantly hurt performance.
::warning
Avoid making Three.js objects or their properties deeply reactive. Vue will try to track every change, which is inefficient for objects updated every frame.
::
Instead, use `shallowRef` for Three.js objects. This keeps the reference reactive, but does not make the internal properties reactive, which is much more efficient.
### β Incorrect: Deep Reactivity
```vue [incorrect-reactivity.vue]
```
### β Correct: Use shallowRef and Direct Assignment
```vue [correct-reactivity.vue]
```
::note
**Tip:** Only the `.value` of a `shallowRef` is reactive. The internal properties are not, which is ideal for Three.js objects that are updated frequently.
::
## Dispose Resources with `dispose()`
When a resource is no longer neededβsuch as a texture, geometry, or materialβbe sure to dispose of it to free up memory. This is especially important if your app frequently creates and destroys resources, like in games or interactive experiences.
TresJS will automatically dispose of resources recursively when the component is unmounted, but you can also perform this manually by calling the `dispose()` directly from the package:
::warning
To avoid errors and unwanted side effects, resources created programmatically with the use of `primitives` need to be manually disposed.
::
```vue [manual-dispose.vue]
```
::note
**Best Practice:** Always clean up resources when they are no longer needed to prevent memory leaks and keep your application performant.
::
# Primitives
## What are Primitives?
The `` component is a versatile low-level component in TresJS that allows you to directly use any Three.js object within your Vue application without an abstraction. It acts as a bridge between Vue's reactivity system and THREE's scene graph.
This component is particularly useful when you need:
- **Direct Three.js Integration**: Use existing Three.js objects without wrapper components
- **Complex Model Rendering**: Display models loaded from external sources like GLTF files
- **Performance Optimization**: Bypass component overhead for specific use cases
- **Third-party Library Integration**: Integrate objects from Three.js ecosystem libraries
## Basic Usage
The simplest way to use the `` component is by passing a Three.js object to the `object` prop:
```vue [basic-primitive.vue]
```
## Props
The `` component accepts the following props:
### `object`
- **Type**: `Object3D | Ref`
- **Required**: `true`
The primary Three.js object which the primitive component will render. This should be either a plain Three.js object or a reactive reference (preferably `shallowRef`).
```vue
```
### `dispose`
- **Type**: `boolean | 'default' | ((self: TresInstance) => void) | null`
- **Default**: `'default'` (no disposal for primitives)
Controls how the primitive's resources are disposed when removed from the scene:
- `'default'` - Default behavior: don't dispose primitive resources
- `false` or `null` - Explicitly disable disposal
- `true` - Force disposal of the primitive and its resources
- `function` - Custom disposal function
```vue
```
### `attach`
- **Type**: `string | ((parent: any, self: TresInstance) => () => void)`
- **Optional**
Specifies how to attach the primitive to its parent. Can be a property name or a custom attachment function.
::code-group
```vue [Material Attachment]
```
```vue [Geometry Attachment]
```
```vue [Custom Attachment Function]
```
::
### `visible`
- **Type**: `boolean`
- **Default**: `true`
Controls the visibility of the primitive object.
```vue
```
### Pass-through Props
Any other props are passed through to the underlying Three.js object, allowing you to modify its properties directly:
```vue
```
## Events
The `` component supports all the pointer events available on TresJS components, allowing you to interact with the object in the scene:
```vue [primitive-events.vue]
```
:read-more{to="https://docs.tresjs.org/api/events/pointer-events"}
## Children via Slots
You can add children to the `` component using slots. This is particularly useful when you want to add additional objects or materials which are not part of the main object:
```vue [primitive-children.vue]
```
## Usage with Models
The `` component is especially powerful when working with complex models loaded from external sources. Here's how to use it with GLTF models:
::code-group
```vue [TheModel.vue]
```
```vue [app.vue]
```
::
### Working with Multiple Model Parts
If you are working with complex models, you may want to access and manipulate specific parts:
```vue [primitive-model-parts.vue]
```
## Performance Considerations
When using primitives, keep these performance tips in mind:
::note
**Reactivity Optimization**: Use [`shallowRef`](https://vuejs.org/api/reactivity-advanced.html#shallowref){rel=""nofollow""} instead of [`ref`](https://vuejs.org/api/reactivity-core.html#ref){rel=""nofollow""} for Three.js objects to avoid deep reactivity overhead, as Three.js objects have complex internal structures that don't benefit from Vue's reactivity system.
::
```vue
```
::warning
**Manual Disposal Required**: TresJS does NOT automatically dispose primitive resources by default. This is intentional to avoid altering the user's `:object`. You must manually dispose of geometries, materials, and textures when they're no longer needed to prevent memory leaks.
::
```vue
```
### Controlling Disposal Behavior
You can override the default behavior using the `dispose` prop:
```vue
```
## Common Use Cases
### 1. Integrating Third-party Libraries
```vue
```
### 2. Custom Geometries
```vue
```
### 3. Particle Systems
```vue
```
## Customizing the Primitive Component Name
By default, TresJS provides the `` component for rendering Three.js objects. You can customize the component name using the `primitivePrefix` option in ``:
```vue [custom-primitive-prefix.vue]
```
This feature is useful when:
- You need to avoid naming conflicts with other libraries or components
- Your project requires a specific naming convention
- You want to add TypeScript support for custom primitive names
::note
**TypeScript Support**: When using a custom prefix, you can extend Vue's global components type to get full intellisense support:
```typescript
// types/tres.d.ts
import type { TresPrimitive } from '@tresjs/core'
import type { DefineComponent } from 'vue'
declare module 'vue' {
interface GlobalComponents {
myprimitive: DefineComponent
}
}
export {}
```
::
The `` component provides the flexibility to integrate any Three.js object seamlessly into your TresJS application while maintaining the benefits of Vue's reactivity and component system.
# WebGPU
::warning
**Experimental Feature**: WebGPU support in TresJS is experimental and requires modern browser support. WebGPU is still being developed and may have breaking changes.
::
## What is WebGPU?
[WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API){rel=""nofollow""} is the next-generation graphics API for the web, designed to provide high-performance 3D graphics and general-purpose computing capabilities directly in web browsers. It offers several advantages over WebGL:
### **Key Benefits**
- **Better Performance**: More efficient GPU utilization and reduced CPU overhead
- **Modern GPU Features**: Access to compute shaders, advanced texturing, and modern GPU capabilities
- **Unified API**: Single API for both graphics and compute operations
- **Better Debugging**: Improved error handling and debugging capabilities
- **Future-Proof**: Designed for modern GPU architectures
### **Browser Support**
WebGPU is currently supported in:
- **Chrome/Edge**: Stable support (Chrome 113+)
- **Firefox**: Behind experimental flag
- **Safari**: Experimental support in Safari Technology Preview
::note
Check current WebGPU browser support at [Can I Use WebGPU](https://caniuse.com/webgpu){rel=""nofollow""} and the official [WebGPU support matrix](https://github.com/gpuweb/gpuweb/wiki/Implementation-Status){rel=""nofollow""}.
::
## Usage with TresJS
TresJS supports WebGPU through Three.js's WebGPU renderer. You can enable WebGPU by providing a custom renderer factory to the `` component.
### Basic Setup
```vue [basic-webgpu.vue]
```
### Advanced WebGPU Example
:examples-web-gpu
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
---
:::code-tree{default-value="app.vue"}
```vue [components/HologramCube.vue]
```
```vue [app.vue]
```
:::
::
# TresJS CLI
`@tresjs/cli` is the command line companion for TresJS projects. It exposes a single `tres`
binary that automates the parts of a 3D project that are tedious to write by hand, starting
with turning a `.glb`/`.gltf` model into a real Vue component.
## Installation
Run it without installing anything:
::code-group
```bash [npm]
npx @tresjs/cli --help
```
```bash [yarn]
yarn dlx @tresjs/cli --help
```
```bash [pnpm]
pnpm dlx @tresjs/cli --help
```
::
Or add it to the project so everyone on the team runs the same version:
::code-group
```bash [npm]
npm install -D @tresjs/cli
```
```bash [yarn]
yarn add -D @tresjs/cli
```
```bash [pnpm]
pnpm add -D @tresjs/cli
```
::
Once installed, the binary is available as `tres`:
```bash
tres --help
```
::prose-note
The CLI requires **Node.js 20 or later**.
::
## Commands
::card-group
:::card
---
spotlight: true
icon: i-lucide-box
title: tres gltf
to: https://docs.tresjs.org/cli/gltf
---
Generate a typed Vue component from a `.glb`/`.gltf` model, with a slot per node so your
overrides survive the next export.
:::
::
```bash
tres gltf public/models/robot.glb
# β² β β Tres gltf robot.glb
#
# β Parse 12 named nodes Β· 3 meshes Β· 2 materials 34ms
# β Emit 3 slots 1ms
#
# β src/models/Robot.gen.vue
# slots Head, Body, Base
#
# Done in 41ms
```
## Global options
| Option | Description |
| :-------------- | :--------------------------------------------------------------------- |
| `-V, --version` | Print the CLI version. |
| `-h, --help` | Print help for the CLI or for a specific command (`tres gltf --help`). |
::prose-note
Looking for a way to **scaffold a new project**? That is a different tool: `npm create tres@latest`.
See the [installation guide](https://docs.tresjs.org/getting-started/installation).
::
## Piping
Progress (the header, the phases, warnings, the file list) goes to **stderr**. **stdout** carries
only what a command produces: the JSON of `--json`, the component of `--console`. So redirecting
gives you the payload alone, while you still watch the run on screen:
```bash
tres gltf public/models/robot.glb --json > robot.json
tres gltf public/models/robot.glb --console | pbcopy
```
Colour follows [`NO_COLOR`](https://no-color.org/){rel=""nofollow""}, and the spinner turns itself off when stderr
is not a terminal, so CI logs get one line per phase instead of a repaint per frame.
# tres gltf
`tres gltf` turns a model into a Vue component you can read, diff and type-check, instead of a
`` black box.
```bash
tres gltf public/models/robot.glb
# β² β β Tres gltf robot.glb
#
# β Parse 12 named nodes Β· 3 meshes Β· 2 materials 34ms
# β Emit 3 slots 1ms
#
# β src/models/Robot.gen.vue
# slots Head, Body, Base
#
# Done in 41ms
```
The slot list is capped at the first six names, since a level with sixty of them would bury the
paths above it. `--verbose` prints them all, and the generated file carries the authoritative
list in its `defineSlots<{ β¦ }>`.
Then use it like any other component:
```vue [App.vue]
```
## What gets generated
Every node in the model becomes a real element, and every node the artist named becomes a
`` whose fallback is the generated markup:
```vue [src/models/Mug.gen.vue]
```
A few things are worth pointing out:
::prose-list
- **`ModelNodes` and `ModelMaterials`** describe *this* export, so `nodes.Mug` is a `Group` and not an `any`.
- **Draco is detected**, and `{ draco: true }` is passed automatically. A Draco model renders nothing without it.
- **Lights, cameras and bones** are emitted as ``: their props live on the parsed object, and a bone must *be* the parsed object for skinning to work.
- **The root `TresGroup` stays mounted** and gates its children with `v-if="!isLoading"`, so refs on it are stable.
- **`@ready` fires once per load**, carrying `{ nodes, materials }`, and `isReady` exposes the same state as a value. An event that already fired cannot be read after the fact, which is what `isReady` is for: a parent that binds late, or a state machine that wants to ask later. Refetching the model (`useGLTF` exposes `execute()`) puts `isReady` back to `false` and arms `ready` again.
- **Readiness reads `state`, not just `isLoading`.** `isLoading` is cleared in a `finally`, so a 404 clears it exactly like a success would; `state` is only set when the load produced a scene. Without that term a failed load would call the model ready with empty `nodes` and `materials`.
::
## Overriding a node
The generated file is disposable. Anything you want to change lives in the parent, as a slot
override:
```vue [App.vue]
```
Re-run `tres gltf` after the artist re-exports and the override survives, because it never lived
in the generated file. If the artist *renames* `Head`, the override becomes a type error instead
of silently disappearing at runtime.
::prose-note
`--slots named` (the default) skips exporter noise like `Object_12`, `Sketchfab_model` or
`Armature`, since overriding those means nothing. On marketplace assets where **every** mesh is
exporter-named, the CLI says so and points you at `--slots all`.
::
## Nodes and materials from the parent
The component exposes `nodes` and `materials`, so a parent can reach into the model without
re-loading it:
```vue
```
::prose-note
If you only need them the moment the model is ready, `@ready="({ nodes, materials }) => β¦"` hands
you the same pair without a ref. The exposed `nodes` and `materials` are for reaching in at any
point after.
::
## Animated models
When the model ships animation clips, the generated component also wires up
[`useAnimations`](https://cientos.tresjs.org/api/miscellaneous/use-animations){rel=""nofollow""} and hands the
bound `actions` to `@ready`, keyed by a union of the clip names:
```ts
type ActionName
= | 'Idle'
| 'Walk'
| 'Death_A'
```
`@ready` fires once per load, after the clips have loaded and the mixer has bound its actions, with
`{ nodes, materials, actions }`. No template ref, and `actions.Idle` is there to play:
```vue [App.vue]
actions.Idle?.play()" />
```
Prefer the event over reaching through a ref. The mixer binds its actions a flush after the clips
land (`useAnimations` runs `flush: 'post'` for exactly this), so a handler that reads `actions`
too early sees them undefined. `@ready` waits for the binding; `isReady` exposes the same state
as a value, `false` until it holds and reset on any refetch, for a parent that binds late or asks
again later.
A mixer resolves every track against a node **name**, so the CLI keeps the name of every node a
clip drives, whatever `--slots` or `--keepnames` say. A group that exists only to be animated
survives pruning for the same reason:
```vue
```
Nodes no clip mentions are unaffected, so this costs nothing on a model with no animation on it.
Batched nodes keep their names too: see [instancing](https://docs.tresjs.org/#instancing-repeated-meshes).
### Clips from separate files
Mixamo, KayKit and Quaternius all ship the mesh in one file and the clips in others, so a
mesh-only export has nothing to wire. Generating one says so:
```bash
tres gltf public/models/Dummy.glb
# β This model is skinned but carries no animation clips. Pass --animations to wire in clips exported to separate files.
```
Point `--animations` at the clip files, once per file. A KayKit character, whose 39 clips ship
as three libraries beside the rig:
```bash
tres gltf public/models/Dummy.glb \
--animations public/models/animations/Rig_Medium_General.glb \
--animations public/models/animations/Rig_Medium_MovementBasic.glb \
--animations public/models/animations/Rig_Medium_MovementAdvanced.glb
# β² β β Tres gltf Dummy.glb
#
# β Parse 51 named nodes Β· 8 meshes Β· 1 material Β· 37 clips merged 37ms
# β Emit 6 slots 1ms
#
# β src/models/Dummy.gen.vue
# slots Dummy_ArmLeft, Dummy_ArmRight, Dummy_Body, Dummy_Head,
# Dummy_LegLeft, Dummy_LegRight
# clips Death_A, Death_A_Pose, Death_B, Death_B_Pose, Hit_A, Hit_B,
# β¦ 31 more β rerun with --verbose
#
# Done in 87ms
```
The merged names are the ones `ActionName` will carry, so they are printed back; `--verbose`
lists all of them.
The component loads each one and merges the clips into a single array, the model's own first:
```ts
const { nodes, materials, isLoading } = useGLTF('/models/Dummy.glb')
const { state: rigMediumGeneral, isLoading: rigMediumGeneralLoading } = useGLTF('/models/animations/Rig_Medium_General.glb')
const { state: rigMediumMovementBasic, isLoading: rigMediumMovementBasicLoading } = useGLTF('/models/animations/Rig_Medium_MovementBasic.glb')
const { state: rigMediumMovementAdvanced, isLoading: rigMediumMovementAdvancedLoading } = useGLTF('/models/animations/Rig_Medium_MovementAdvanced.glb')
const animations = computed(() => {
// The mixer resolves every track against a node name in the rendered tree and never
// retries a miss, so the clips must not reach it before the model they drive.
if (isLoading.value) {
return []
}
return [
...(rigMediumGeneral.value?.animations ?? []),
...(rigMediumMovementBasic.value?.animations ?? []),
...(rigMediumMovementAdvanced.value?.animations ?? []),
]
})
```
That guard matters: a clip library is a fraction of the size of the model it drives, so its
files arrive first. Handing a mixer clips before the tree exists binds every track to nothing,
and three caches the miss instead of retrying it.
`ActionName` becomes the union across every file, and the node names the external clips drive
survive pruning exactly like the model's own would. Each file gets its own url, inferred from
`public/` the same way the model's is, and its own `{ draco: true }` when it is compressed.
This is where `@ready` earns its place over a hand-rolled watch. Each clip file resolves on its
own, so `actions` can populate with the first library's clips and repopulate as the others land.
The generated gate names every condition it waits for, and holds until all of them hold:
```ts
const isReady = ref(false)
watch(
() => !isLoading.value
&& !rigMediumGeneralLoading.value
&& !rigMediumMovementBasicLoading.value
&& !rigMediumMovementAdvancedLoading.value
&& state.value !== null
&& Object.keys(actions).length > 0,
(ready) => {
if (!ready) {
isReady.value = false
return
}
// β¦emit('ready', { nodes: nodes.value, materials: materials.value, actions })
},
{ flush: 'post', immediate: true },
)
```
A watch that fired on the first non-empty `actions` would see only the General clips, and
`actions.Jump_Start` (a MovementBasic clip) would be `undefined`. `@ready` waits for the lot.
The `state` term is doing something less obvious. The actions bind against the root group, which
stays mounted whatever the load did, so clips from a `--animations` file fill `actions` even when
the **model** itself 404s. Without that term a rig that never arrived would still report ready.
#### When two files carry the same clip name
Clip libraries overlap β the three above all ship a `T-Pose`. The array decides: a mixer keys
`actions` walking it, so the **last** file passed wins, and an `--animations` clip always
overrides one the model came with. `ActionName` lists the name once. The CLI says which file
won rather than leaving it to be discovered:
```bash
# β Both Rig_Medium_General.glb and Rig_Medium_MovementBasic.glb carry "T-Pose". Rig_Medium_MovementBasic.glb is merged last, so its clip is the one that plays.
```
Pass the file you want to win last.
::prose-note
The CLI parses the clip files too, so it can compare each clip's track targets against the
model's node names β the one animation failure that is completely silent at runtime. A clip that
drives nodes this rig does not have gets a warning; a clip where **nothing** binds is left out of
`ActionName` entirely, since it could never play.
::
## Where the file is written
By default the component is written next to the model as `.gen.vue`, with one exception:
if the model lives under `public/`, the component goes to `src/models/` (or `app/models/` on
Nuxt) instead. A bundler copies `public/` verbatim and never compiles it, so a component written
there could never be imported.
```bash
tres gltf public/models/robot.glb # β src/models/Robot.gen.vue
tres gltf src/assets/robot.glb # β src/assets/Robot.gen.vue
tres gltf public/models/robot.glb -o src/components/Robot.vue
```
`models/` rather than `components/` is deliberate: component directories are auto-scanned by
`unplugin-vue-components` and Nuxt, and the dot in `.gen.vue` produces an invalid identifier in
the generated `components.d.ts`.
::prose-warning
The CLI refuses to overwrite a file it did not generate. Pass `--force` to replace a
hand-written file anyway.
::
## The model url
The url baked into `useGLTF` is inferred from the nearest `public/` directory above the model:
`public/models/robot.glb` becomes `/models/robot.glb`. With no `public/` in sight the CLI warns
and guesses, so set it yourself:
```bash
tres gltf assets/robot.glb --url https://cdn.example.com/robot.glb
```
## Optimizing the model
`--transform` runs the model through [glTF-Transform](https://github.com/donmccurdy/glTF-Transform){rel=""nofollow""}
(dedup, weld, texture resize and compression, Draco, and more) before generating. Savings of
70β90% are typical:
```bash
tres gltf public/models/robot.glb --transform
# β² β β Tres gltf robot.glb
#
# β Transform 755KB βΊ 40KB -95% 1.2s
# β Parse 12 named nodes Β· 3 meshes Β· 2 materials 34ms
# β Emit 3 slots 1ms
#
# β src/models/Robot.gen.vue
# slots Head, Body, Base
# useGLTF() now loads robot-transformed.glb
#
# Done in 1.3s
```
While it runs, the `Transform` line names the step it is on (`compressing textures`,
`encoding draco`, and the rest), so a slow model is never a silent one.
The optimized file is written **beside the source** as `-transformed.glb`; the original is
never touched. The generated `useGLTF()` points at the optimized file, and since the output is
Draco-compressed it gets `{ draco: true }` automatically.
::prose-list
- **`--resolution `** caps texture size (default `1024`), and **`--format `** picks the codec: `webp` (default), `jpeg`, `png` or `avif`.
- **`--simplify`** reduces geometry with meshoptimizer, tuned by `--ratio` (fraction of vertices to keep) and `--error` (error ceiling, as a fraction of mesh radius).
- **`--keepmeshes`** and **`--keepmaterials`** switch off mesh joining and material batching, which is what you want when the node structure has to survive.
::
::prose-warning
Joining and simplification change the node graph, so node names (and therefore slot names) can
move. Decide on `--transform` **before** you write slot overrides against them.
::
## Instancing repeated meshes
`--instance` collapses meshes that share a geometry and a material into a single
`InstancedMesh`. Because one SFC is one component, this restructures the output into **two**
files: a provider that owns the load and the batches, and the model that renders into them.
```bash
tres gltf public/models/robot.glb --instance
# β² β β Tres gltf robot.glb
#
# instancing needs deduplicated geometry, so --transform is on and --keepmeshes with it
# β Transform 755KB βΊ 48KB -94% 1.1s
# β Parse 12 named nodes Β· 3 meshes Β· 2 materials 31ms
# β Emit 3 slots 1ms
#
# β src/models/Robot.instances.gen.vue β owns the load and the batches
# β src/models/Robot.gen.vue β renders against them
```
The payoff is across copies, not inside one. Wrap them in the provider and the model is loaded
and parsed once, with every copy costing the drawcalls of one:
```vue [App.vue]
```
The provider is found through `provide`/`inject`, so the copies can sit anywhere below it. A
`` rendered **outside** its provider throws with a message saying so, rather than
rendering nothing.
Each copy still emits its own `@ready` and exposes `isReady`. The provider owns the load, so the
model reads the injected data instead of an `isLoading` of its own: ready follows the actions
binding when the model is animated, and the nodes populating when it is not.
`--instance` only batches a geometry that two or more meshes share. `--instanceall` batches
every eligible mesh, including the ones that appear once, which pays off when the whole model is
on screen many times. Skinned meshes and meshes with morph targets are never batched: an
`InstancedMesh` has nowhere to put per-mesh skeletons or morph influences.
::prose-warning
**Instancing turns `--transform` on.** Batching dedupes by geometry identity, and an unoptimized
export hands three.js one geometry object per node however identical they are, so instancing
without the pipeline finds nothing to batch. `--keepmeshes` comes with it, since joining would
weld the repeats into a single mesh and leave nothing to batch either.
::
::prose-note
**Batching does not cost a clip its target.** An `` is a real node with a real `name`,
and the batch re-reads every instance's world matrix each frame, so a mixer drives batched
geometry exactly as it drives a mesh. `batch` is what it joins, `name` is what it is called: a
bucket is keyed after its first mesh, so the two differ as soon as more than one mesh shares a
geometry.
::
### Overriding an instanced node
Slots still work, but a batched node has no geometry or material of its own to hand you. It gets
the batch's instead, its placement, and `batch`: the key its `InstancedMesh` registered under.
That key is what an override needs to stay in the batch:
```vue [App.vue]
```
Leaving the batch is **one drawcall traded for control**. That is exactly what the geometry and
material bindings are for, but it is not free.
::prose-warning
**Pass `batch`, never the slot name.** A bucket's batch is keyed after its first mesh, so a slot
named `Screw_3` may belong to the batch `Screw_0`. An `` whose `batch` matches nothing
registers with nothing and renders nothing, so it warns in the console rather than going quiet.
::
::prose-note
Lights and cameras are emitted as ``, and an `Object3D` has one parent,
so a second copy of the model steals them from the first. The CLI warns when an instanced model
contains any and suggests generating from a subtree with `--root`.
::
## Inspecting a model
`--dry-run` reports what the parser sees and writes nothing:
```bash
tres gltf public/models/artificer.glb --dry-run
# β² β β Tres gltf artificer.glb
#
# β Parse 51 named nodes Β· 8 meshes Β· 1 material 53ms
# 76 animation clips
# run without --dry-run to generate a component
```
Models with meshes that share a geometry and material also report how many *instancing
candidates* they have, which is what `--instance` would batch. With `--animations`, each clip
file is counted on its own line and the merged total below them β the total is not the sum: a
name in two files counts once, and a clip nothing binds counts not at all.
```bash
tres gltf public/models/Dummy.glb \
-a public/models/animations/Rig_Medium_General.glb \
-a public/models/animations/Rig_Medium_MovementBasic.glb \
-a public/models/animations/Rig_Medium_MovementAdvanced.glb \
--dry-run
# β² β β Tres gltf Dummy.glb
#
# β Parse 51 named nodes Β· 8 meshes Β· 1 material Β· 37 clips merged 29ms
# 0 animation clips
# + Rig_Medium_General.glb: 15 clips
# + Rig_Medium_MovementBasic.glb: 11 clips
# + Rig_Medium_MovementAdvanced.glb: 13 clips
# 37 clips merged
# run without --dry-run to generate a component
```
`--json` dumps the full parse, and `--console` prints the component to stdout instead of writing
it, which is handy for piping or for a quick look before committing. With `--instance`, both
halves are printed, separated by the filename the provider would have been written to.
## Options
| Flag | Default | Description |
| :------------------------- | :---------------------- | :---------------------------------------------------------------------------------------------- |
| `-o, --output ` | `.gen.vue` | Where to write the component. |
| `-u, --url ` | inferred from `public/` | The url the model is served from at runtime. |
| `-a, --animations ` | none | A glb/gltf to take animation clips from, merged with the model's own. Repeatable. |
| `-s, --slots ` | `named` | `named`, `all` or `none`. |
| `--shadows` | `false` | Add `cast-shadow` and `receive-shadow` to every mesh. |
| `-K, --keepgroups` | `false` | Keep pass-through groups that carry nothing but nesting. |
| `-k, --keepnames` | `false` | Keep `name` attributes on the emitted elements. |
| `-r, --root ` | scene root | Generate from this subtree only. |
| `-p, --precision ` | `2` | Fractional digits kept on transforms. |
| `-m, --meta` | `false` | Emit glTF `extras` as `:user-data`. |
| `-c, --console` | `false` | Print the component instead of writing it. |
| `-f, --force` | `false` | Overwrite a file this tool did not generate. |
| `-v, --verbose` | `false` | List every slot and clip name instead of the first few. |
| `-T, --transform` | `false` | Optimize the model into a separate `-transformed.glb` and generate against it. |
| `-i, --instance` | `false` | Batch meshes that share a geometry and material into an `InstancedMesh`. Implies `--transform`. |
| `-I, --instanceall` | `false` | Batch every eligible mesh, even the ones that appear once. Implies `--transform`. |
| `--resolution ` | `1024` | Max texture size when transforming. |
| `--format ` | `webp` | Texture format when transforming: `webp`, `jpeg`, `png` or `avif`. |
| `--simplify` | `false` | Reduce geometry with meshoptimizer when transforming. |
| `--ratio ` | `0` | Target fraction of vertices to keep with `--simplify`. `0` keeps as few as the error allows. |
| `--error ` | `0.001` | Error ceiling with `--simplify`, as a fraction of mesh radius. |
| `--keepmeshes` | `false` | Do not merge meshes when transforming. |
| `--keepmaterials` | `false` | Do not batch materials when transforming. |
| `--dry-run` | `false` | Report what the parser sees, generate nothing. |
| `--json` | `false` | Print the full parse as JSON. |
## Supported models
Both `.glb` and unpacked `.gltf` + `.bin` + textures work, as do Draco-compressed models. Pass
the **path to the file**, not the url it is served from:
```bash
tres gltf /models/robot.glb # β that is the url
tres gltf public/models/robot.glb # β that is the file
```
::read-more{to="https://cientos.tresjs.org/getting-started/loading-models"}
Compare this workflow with `useGLTF` and `GLTFModel` in the cientos docs.
::
# OrbitControls
:examples-orbit-controls
OrbitControls is a camera controller that allows you to orbit around a target. It's a great way to explore your scene interactively.
::note
The OrbitControls utility, which allows users to easily navigate around a 3D scene, isn't included in the core Three.js library by default. To use it, you need to import it manually from the `three/addons/controls/OrbitControls` module via the `three-stdlib` package. Alternatively, if you're using the TresJS ecosystem, you can opt for a ready-to-use version of OrbitControls available as a component in the `@tresjs/cientos` package.
::
## Using OrbitControls Manually
To use `OrbitControls` manually, import it and extend the catalog:
```ts [setup.ts]
import { extend } from '@tresjs/core'
import { OrbitControls } from 'three/addons/controls/OrbitControls'
extend({ OrbitControls })
```
Now you can use the `TresOrbitControls` component in your scene. Since [OrbitControls](https://threejs.org/docs/#examples/en/controls/OrbitControls){rel=""nofollow""} needs a reference to the camera and renderer, you can use the `useTres` composable:
::code-group
```vue [OrbitControls.vue]
```
```vue [App.vue]
```
::
:read-more{to="https://docs.tresjs.org/api/components/tres-objects#extending-the-catalogue"}
## OrbitControls from `@tresjs/cientos` (Recommended)
The [`@tresjs/cientos`](https://cientos.tresjs.org/){rel=""nofollow""} package provides a plug-and-play `` component that wraps the ThreeJS OrbitControls. You don't need to extend the catalog or pass any argumentsβit just works!
```vue [CientosOrbitControls.vue]
```
::tip
Make sure the `PerspectiveCamera` is set first in the canvas, otherwise controls might not work as expected.
::
# Basic Animations
:examples-basic-animation
This recipe covers the fundamentals of creating smooth animations in TresJS.
::steps
### Import `useLoop` composable
The `useLoop` composable is the core of TresJS updates, which includes: **animations**. It allows you to register a callback that will be called every time the renderer updates the scene with the browser's refresh rate.
```ts
import { useLoop } from '@tresjs/core'
const { onBeforeRender } = useLoop()
onBeforeRender(() => {
// Animation logic here
})
```
:read-more{to="https://docs.tresjs.org/api/composables/use-loop"}
### Get a reference to the object you want to animate
Similar to Vue, you can use [template refs](https://vuejs.org/guide/essentials/template-refs){rel=""nofollow""} to access the Three.js object instance and manipulate its properties. If you want to optimize even further, you can use `shallowRef` to avoid unnecessary reactivity.
```vue [RotatingCube.vue]
```
:::read-more
---
to: https://docs.tresjs.org/api/advanced/performance#reactivity-and-performance
---
To read more about reactivity and performance in TresJS.
:::
## Use `delta`
The `onBeforeRender` callback provides a `delta` parameter, which represents the time elapsed since the last frame. This is useful for creating frame rate independent animations.
```ts
onBeforeRender(({ delta }) => {
if (cubeRef.value) {
cubeRef.value.rotation.x += delta
cubeRef.value.rotation.y += delta
}
})
```
Without using `delta`, the animation speed would vary depending on the frame rate, leading to inconsistent behavior across different devices, like the example below:
:::div{.w-full.flex.border.border-gray-200.rounded-lg.overflow-hidden}
::::div{.w-1/2.border-r.border-gray-200}
:::::div{.text-center.p-2.border-b.border-gray-200.font-bold}
60fps
:::::
:examples-basic-animation-60fps
::::
::::div{.w-1/2}
:::::div{.p-2.text-center.p-2.border-b.border-gray-200.font-bold}
120fps
:::::
:examples-basic-animation-120fps
::::
:::
:::div{.p-2.text-xs.text-gray-500.italic.mt-2.block.text-center}
The value of \`delta\` is 0.016 for 60fps and 0.008 for 120fps, this difference ensures that the cube rotates at the same speed on both frame rates.
:::
## Using `elapsed`
The `onBeforeRender` callback also provides an `elapsed` parameter, which represents the total time elapsed since the start of the animation. This can be useful for creating time-based animations like oscillations.
```ts
onBeforeRender(({ elapsed }) => {
if (cubeRef.value) {
cubeRef.value.position.y += Math.sin(elapsed) * 0.01
}
})
```
:examples-basic-animation-elapsed
::
# Model Animation
:examples-model-animation
Let's bring your 3D models to life with animations in TresJS. This guide covers loading animated models and controlling their playback.
## Where to find models?
You can find quality 3D models for your projects in various online repositories. Here are some hand picked sources from the TresJS community:
- [poly.pizza](https://poly.pizza/){rel=""nofollow""} - A collection of free 3D models.
- [Pmndrs Marketplace](https://market.pmnd.rs/){rel=""nofollow""} - A marketplace for 3D assets. All free
- [KayKit Character Packs](https://kaylousberg.itch.io/){rel=""nofollow""} - Free and paid character packs. Animated, all CC0, a personal favorite of ours.
For this tutorial we will use a simplified version of the **KayKit Knight character**, you can download it directly from :u-button[here]{:to="/models/knight/Knight.glb" download="true" external="" icon="i-lucide-arrow-down" variant="ghost"}.

## Loading Animated Models
::steps
### Install cientos
If you haven't already, install the `@tresjs/cientos` package, which provides components for loading 3D models.
```bash
npm install @tresjs/cientos
```
### Load the Model
Use the `useGLTF` composable from `@tresjs/cientos` to load your animated model. Add the downloaded GLB file to your public directory in a subfolder like `/models/knight/`.
```vue [Knight.vue]
```
:::read-more{to="https://cientos.tresjs.org/api/loaders/use-gltf"}
Learn more about the `useGLTF` composable.
:::
:::tip
Instead of wiring the model up by hand, you can generate a typed component from it with
`npx @tresjs/cli gltf public/models/knight/Knight.glb`. It exposes the clips as
`actions`, keyed by a union of their names. See [`tres gltf`](https://docs.tresjs.org/cli/gltf).
:::
### Add Rig to the Scene
The Rig is the root object that contains the entire model and its animations. You can access it from the individual nodes.
:::tip
The Rig might be named differently depending on the model. Check the model's structure to find the correct root object.
:::
```vue [Knight.vue]
```
::
## Animation Control
::steps
### Use the `useAnimations` Composable
The `useAnimations` composable helps manage and play animations from your model. It takes the animations array and the rig as parameters and returns the `AnimationClips` (actions).
```vue [Knight.vue]
```
:::read-more{to="https://cientos.tresjs.org/api/miscellaneous/use-animations"}
Learn more about the `useAnimations` composable.
:::
### Play an Animation
You can play an animation by calling the `play` method on the desired action. For example, to play the "Cheer" animation:
```ts
const { actions } = useAnimations(animations, rig)
actions.Cheer?.play()
```
### Set animation loop
You can set the loop mode of an animation using the `setLoop` method. For example, to make the "Cheer" animation loop indefinitely:
```ts
actions.Cheer?.setLoop(THREE.LoopRepeat, Infinity)
actions.Cheer?.play()
```
### Smooth Animation Transitions
To create smooth transitions between animations, use the `fadeIn` and `fadeOut` methods. This prevents abrupt changes and creates more natural character movement:
```vue [Knight.vue]
```
**Key points about animation transitions:**
- `fadeOut(duration)` gradually reduces the animation's influence over the specified duration
- `fadeIn(duration)` gradually increases the animation's influence over the specified duration
- `reset()` resets the animation to its starting frame
- `setEffectiveWeight(1)` ensures the animation has full influence when active
- Shorter durations (0.1-0.3s) work well for quick actions, longer ones (0.5-1s) for smoother character transitions
::
# Advanced GSAP Animations
:examples-advanced-gsap-animations
This recipe demonstrates how to create sophisticated animations using GSAP (GreenSock Animation Platform) with TresJS for smooth, performance-optimized animations with advanced features like staggering and timeline control.
::steps
### Install GSAP
First, install GSAP as a dependency in your project:
:::code-group
```bash [npm]
npm install gsap
```
```bash [yarn]
yarn add gsap
```
```bash [pnpm]
pnpm install gsap
```
:::
### Import required modules
Import GSAP and the necessary Vue composables. Use `shallowRef` for better performance with Three.js objects:
```ts
import { shallowRef, watch } from 'vue'
import { OrbitControls } from '@tresjs/cientos'
import gsap from 'gsap'
```
:::tip
Use `shallowRef` instead of `ref` to avoid unnecessary reactivity on Three.js objects, which improves performance.
:::
### Create multiple objects to animate
Set up an array of positions for multiple boxes that will be animated with stagger effects:
```ts
const boxesRef = shallowRef()
const zs = []
for (let z = -4.5; z <= 4.5; z++) {
zs.push(z)
}
```
### Set up the scene structure
Create a group of meshes that will be animated together:
```vue
```
### Create the GSAP staggered animation
Use Vue's `watch` to set up the animation when the template ref is available:
```ts
watch(boxesRef, () => {
if (!boxesRef.value) return
// Get positions and rotations for all boxes
const positions = Array.from(boxesRef.value.children).map(
(child) => child.position
)
const rotations = Array.from(boxesRef.value.children).map(
(child) => child.rotation
)
const animProperties = {
ease: 'power1.inOut',
duration: 1,
stagger: {
each: 0.25,
repeat: -1,
yoyo: true,
},
}
// Animate positions
gsap.to(positions, {
y: 2.5,
...animProperties,
})
// Animate rotations
gsap.to(rotations, {
x: 2,
...animProperties,
})
})
```
### Understanding GSAP Stagger Options
The `stagger` property provides powerful control over timing:
```ts
const animProperties = {
ease: 'power1.inOut', // Easing function
duration: 1, // Animation duration in seconds
stagger: {
each: 0.25, // Delay between each object (0.25s)
repeat: -1, // Infinite repeat (-1)
yoyo: true, // Reverse on alternate cycles
from: 'start', // Animation direction (start, center, end)
},
}
```
:::read-more{to="https://gsap.com/docs/v3/Staggers/"}
Learn more about GSAP stagger options and configurations.
:::
::
## Advanced Techniques
### Timeline Control
:examples-advanced-gsap-timeline
For more complex sequences, use GSAP timelines to coordinate multiple animations:
```vue [TimelineAnimation.vue]
```
### Performance Optimization
When animating many objects, optimize performance by:
1. **Use `shallowRef`** for Three.js object references
2. **Batch property access** to avoid repeated DOM queries
3. **Use GSAP's `set()` method** for immediate property changes
4. **Leverage hardware acceleration** with `force3D: true`
```ts
// Optimized animation setup
const optimizedAnimation = () => {
// Get all properties at once
const meshes = Array.from(boxesRef.value.children)
const positions = meshes.map(mesh => mesh.position)
const rotations = meshes.map(mesh => mesh.rotation)
// Use force3D for hardware acceleration
gsap.to(positions, {
y: 2,
duration: 1,
force3D: true,
ease: 'power2.out'
})
}
```
### Animation Events
GSAP provides powerful callback events to sync with your application state:
```ts
gsap.to(positions, {
y: 2,
duration: 1,
stagger: 0.1,
onStart: () => console.log('Animation started'),
onComplete: () => console.log('Animation completed'),
onUpdate: function() {
// Called on every frame
console.log('Progress:', this.progress())
},
onRepeat: () => console.log('Animation repeated')
})
```
::tip
GSAP automatically handles frame rate optimization and provides better performance than manual animations for complex sequences.
::
::read-more{to="https://gsap.com/docs/v3/"}
Explore the full GSAP documentation for advanced features and techniques.
::
# Tweakpane
[Tweakpane](https://tweakpane.github.io/docs/){rel=""nofollow""} is a compact GUI library that provides an excellent way to create interactive controls for your 3D scenes. This recipe shows you how to integrate Tweakpane with TresJS to create dynamic, real-time controls for your 3D objects and scenes.
:examples-tweakpane
## Installation
First, install Tweakpane v4 in your project:
::code-group
```bash [npm]
npm install tweakpane@^4.0.0
```
```bash [yarn]
yarn add tweakpane@^4.0.0
```
```bash [pnpm]
pnpm add tweakpane@^4.0.0
```
::
Additionally, if you are working with TypeScript:
::code-group
```bash [npm]
npm install --save-dev @tweakpane/core
```
```bash [yarn]
yarn add --save-dev @tweakpane/core
```
```bash [pnpm]
pnpm add --save-dev @tweakpane/core
```
::
::tip
Make sure to use Tweakpane v4 or higher, as this recipe uses the latest API which has breaking changes from v3.
::
## Basic Setup
Here's how to set up Tweakpane with a basic TresJS scene:
```vue
```
### Monitoring Values
You can also monitor values without making them editable:
```ts
const stats = ref({
triangles: 0,
fps: 0,
})
const statsFolder = pane.value.addFolder({ title: 'Statistics' })
statsFolder.addMonitor(stats.value, 'triangles')
statsFolder.addMonitor(stats.value, 'fps', { interval: 100 })
```
## Cleanup
Don't forget to dispose of the pane when the component unmounts:
```ts
import { onUnmounted } from 'vue'
onUnmounted(() => {
pane.value?.dispose()
})
```
::tip
Always dispose of the pane instance to prevent memory leaks, especially in SPAs where components are frequently mounted/unmounted.
::
# Dynamic components
:examples-dynamic-transition
This recipe covers how to use [``](https://vuejs.org/guide/built-ins/transition.html#transition){rel=""nofollow""} and [`Dynamic`](https://vuejs.org/guide/essentials/component-basics.html#dynamic-components){rel=""nofollow""} Vue built-in components.
::steps
### Set up our main scene
Let's start with a simple scene.
```vue [main.vue]
```
### Let's create two simple components
I'm going to add some simple animation logic just to add a little more life.
```vue [Box.vue]
```
```vue [Sphere.vue]
```
### Using Vue dynamic components
You can use [dynamic components](https://vuejs.org/guide/essentials/component-basics.html#dynamic-components){rel=""nofollow""} just as you would in Vue.js. There are several ways to do this, but here weβll follow the official Vue example.
- First, we create a ref that contains our **current** component name.
- Then we create another object that will contain **all our [dynamic components].({rel=""nofollow""})**.
- After that, we can define a simple function to change our ref to the new component.
- Don't forget to add the `` to our scene.
```vue [main.vue] {7-14,23}
```
### Adding UI controls to switch components
To be able to switch between components, lets add a floating UI containing buttons to change between the **Box** and the **Sphere**
```vue
```
Let's add a little CSS.
```vue
```
:examples-dynamic-transition-only-dynamic
:::prose-note
You can use [KeepAlive](https://vuejs.org/guide/built-ins/keep-alive.html#keepalive){rel=""nofollow""} if you want component instances to be cached and preserved between component switches
:::
::
::steps
## Adding transitions
### Wrap the component inside the [``](https://vuejs.org/guide/built-ins/transition.html#transition){rel=""nofollow""}
Once we know the power of using dynamic components, we can create more interactive scenes using built-in Vue components! Now let's add a little animation using [`GSAP`](https://gsap.com/){rel=""nofollow""} and [``](https://vuejs.org/guide/built-ins/transition.html#transition){rel=""nofollow""} components.
For that, the first step is to wrap our dynamic component in a [``](https://vuejs.org/guide/built-ins/transition.html#transition){rel=""nofollow""}.
Very important: we need to tell our component that we're going to handle our animations using `JS`, not `CSS`, by using the prop `:css="false"`. Otherwise, the component will search for a DOM element and will fail.
:::prose-note
Elements inside Tres.js live inside a canvas, not in the DOM.
:::
```html
```
### Using JS hooks
Then we can use the provided [JS hooks](https://vuejs.org/guide/built-ins/transition.html#javascript-hooks){rel=""nofollow""}; in this demo, we're going to use `@enter` and `@leave`.
```html
```
### Now it's time to animate!
In case you haven't installed it already, install [GSAP](https://gsap.com/){rel=""nofollow""} as a dependency in your project:
:::code-group
```bash [npm]
npm install gsap
```
```bash [yarn]
yarn add gsap
```
```bash [pnpm]
pnpm install gsap
```
:::
In our `onEnter` and `onLeave` functions, we put our desired animations. As the names indicate, one controls when the element enters the scene and the other when it leaves.
```js
import { gsap } from 'gsap' // don't forget to import GSAP
function onEnter(el) {
gsap.from(el.material, { duration: 1, opacity: 0 })
}
function onLeave(el, done) {
gsap.to(el.material, { duration: 0.05, opacity: 0 })
}
```
:::prose-note
Important: note that animating the `opacity` works here because we set `transparent` in our materials.
:::
## Full example of our main component
```vue