title: Tweakpane description: Learn how to integrate Tweakpane with TresJS for interactive 3D controls
Tweakpane 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 ::
First, install Tweakpane v4 in your project:
::code-group
npm install tweakpane@^4.0.0
yarn add tweakpane@^4.0.0
pnpm add tweakpane@^4.0.0
::
Additionally, if you are working with TypeScript:
::code-group
npm install --save-dev @tweakpane/core
yarn add --save-dev @tweakpane/core
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. ::
Here's how to set up Tweakpane with a basic TresJS scene:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Pane } from 'tweakpane'
import { OrbitControls } from '@tresjs/cientos'
const meshRef = ref()
const pane = ref<Pane>()
const paneContainer = ref<HTMLElement>()
// Reactive properties that will be controlled by Tweakpane
const controls = ref({
positionX: 0,
positionY: 0,
positionZ: 0,
rotationX: 0,
rotationY: 0,
rotationZ: 0,
scale: 1,
color: '#ff6b6b',
wireframe: false,
material: 'basic',
})
onMounted(() => {
if (!paneContainer.value) return
// Create Tweakpane instance with container
pane.value = new Pane({
title: 'Scene Controls',
container: paneContainer.value
})
// Add position controls
const positionFolder = pane.value.addFolder({ title: 'Position' })
positionFolder.addBinding(controls.value, 'positionX', { min: -3, max: 3, step: 0.1 })
positionFolder.addBinding(controls.value, 'positionY', { min: -3, max: 3, step: 0.1 })
positionFolder.addBinding(controls.value, 'positionZ', { min: -3, max: 3, step: 0.1 })
// Add rotation controls
const rotationFolder = pane.value.addFolder({ title: 'Rotation' })
rotationFolder.addBinding(controls.value, 'rotationX', { min: -Math.PI, max: Math.PI, step: 0.01 })
rotationFolder.addBinding(controls.value, 'rotationY', { min: -Math.PI, max: Math.PI, step: 0.01 })
rotationFolder.addBinding(controls.value, 'rotationZ', { min: -Math.PI, max: Math.PI, step: 0.01 })
// Add material controls
const materialFolder = pane.value.addFolder({ title: 'Material' })
materialFolder.addBinding(controls.value, 'scale', { min: 0.1, max: 3, step: 0.1 })
materialFolder.addBinding(controls.value, 'color')
materialFolder.addBinding(controls.value, 'wireframe')
materialFolder.addBinding(controls.value, 'material', {
options: {
Basic: 'basic',
Normal: 'normal',
Standard: 'standard',
},
})
// Add reset button
pane.value.addButton({ title: 'Reset All' }).on('click', () => {
Object.assign(controls.value, {
positionX: 0, positionY: 0, positionZ: 0,
rotationX: 0, rotationY: 0, rotationZ: 0,
scale: 1, color: '#ff6b6b', wireframe: false, material: 'basic'
})
})
})
// Clean up on unmount
onUnmounted(() => {
pane.value?.dispose()
})
</script>
<template>
<div class="relative w-full h-full">
<!-- Tweakpane container positioned outside canvas -->
<div ref="paneContainer" class="absolute top-4 right-4 z-10" />
<TresCanvas clear-color="#82DBC5">
<TresPerspectiveCamera :position="[3, 3, 3]" />
<OrbitControls />
<TresMesh
ref="meshRef"
:position="[controls.positionX, controls.positionY, controls.positionZ]"
:rotation="[controls.rotationX, controls.rotationY, controls.rotationZ]"
:scale="controls.scale"
>
<TresBoxGeometry />
<TresMeshBasicMaterial
v-if="controls.material === 'basic'"
:color="controls.color"
:wireframe="controls.wireframe"
/>
<TresMeshNormalMaterial
v-else-if="controls.material === 'normal'"
:wireframe="controls.wireframe"
/>
<TresMeshStandardMaterial
v-else-if="controls.material === 'standard'"
:color="controls.color"
:wireframe="controls.wireframe"
/>
</TresMesh>
<TresGridHelper :args="[10, 10]" />
<TresAmbientLight :intensity="0.5" />
<TresDirectionalLight :position="[0, 0, 5]" :intensity="0.5" />
</TresCanvas>
</div>
</template>
You can also monitor values without making them editable:
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 })
Don't forget to dispose of the pane when the component unmounts:
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. :::