title: Declarative vs Imperative
TresJS fundamentally changes how you create 3D scenes by transforming Three.js's imperative approach into a declarative, component-based system. This shift makes 3D development more intuitive for Vue developers while maintaining the full power of Three.js.
In traditional Three.js development, you write imperative code - explicit instructions telling the computer exactly what to do step by step:
import * as THREE from 'three'
// Create scene, camera, and renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Create geometry and material
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x00FF00 })
const cube = new THREE.Mesh(geometry, material)
// Add to scene and position
scene.add(cube)
camera.position.z = 5
// Render loop
function animate() {
requestAnimationFrame(animate)
cube.rotation.x += 0.01
cube.rotation.y += 0.01
renderer.render(scene, camera)
}
animate()
TresJS transforms this into declarative code - you describe what you want, not how to achieve it:
<script setup lang="ts">
import { ref } from 'vue'
const cubeRef = ref()
const { onBeforeRender } = useLoop()
function onLoop() {
if (cubeRef.value) {
cubeRef.value.rotation.x += 0.01
cubeRef.value.rotation.y += 0.01
}
}
</script>
<template>
<TresCanvas @loop="onLoop">
<TresPerspectiveCamera :position="[0, 0, 5]" />
<TresMesh ref="cubeRef">
<TresBoxGeometry />
<TresMeshBasicMaterial color="green" />
</TresMesh>
</TresCanvas>
</template>
Let's compare how common 3D operations are handled in both approaches:
::code-group
// Setup scene, camera, renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Add lights
const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1)
directionalLight.position.set(5, 5, 5)
scene.add(directionalLight)
// Create and add objects
const geometry = new THREE.SphereGeometry(1, 32, 32)
const material = new THREE.MeshStandardMaterial({ color: 0xFF6B35 })
const sphere = new THREE.Mesh(geometry, material)
scene.add(sphere)
// Render loop
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<TresCanvas @loop="onLoop">
<TresPerspectiveCamera :position="[0, 0, 5]" />
<!-- Lighting -->
<TresAmbientLight :intensity="0.5" />
<TresDirectionalLight :position="[5, 5, 5]" />
<!-- 3D Object -->
<TresMesh>
<TresSphereGeometry />
<TresMeshStandardMaterial color="#ff6b35" />
</TresMesh>
</TresCanvas>
</template>
::
::code-group
let currentColor = 0xFF0000
let sphere
function changeColor() {
currentColor = currentColor === 0xFF0000 ? 0x00FF00 : 0xFF0000
sphere.material.color.setHex(currentColor)
}
// Manual event handling
document.addEventListener('click', changeColor)
<script setup lang="ts">
import { ref } from 'vue'
const isRed = ref(true)
const color = computed(() => isRed.value ? '#ff0000' : '#00ff00')
const toggleColor = () => {
isRed.value = !isRed.value
}
</script>
<template>
<TresMesh @click="toggleColor">
<TresSphereGeometry />
<TresMeshStandardMaterial :color="color" />
</TresMesh>
</template>
::
Vue's reactivity system ensures that 3D objects always reflect the current state of your data.
Create reusable 3D components that can be easily composed and customized.
<script setup lang="ts">
interface Props {
position?: [number, number, number]
color?: string
radius?: number
}
const props = withDefaults(defineProps<Props>(), {
position: () => [0, 0, 0],
color: '#ff6b35',
radius: 1
})
</script>
<template>
<TresMesh :position="props.position">
<TresSphereGeometry :args="[props.radius]" />
<TresMeshStandardMaterial :color="props.color" />
</TresMesh>
</template>
The Vue DevTools integration allows you to inspect 3D objects and their states visually. The Vue DevTools integration allows you to inspect 3D objects and their states visually.
Hot module replacement during development (almost every time)
TresJS doesn't force you to choose between paradigms. You can combine both approaches when needed using the primitive
component for direct Three.js integration:
<script setup lang="ts">
import { BoxGeometry, Mesh, MeshStandardMaterial } from 'three'
// Create Three.js objects imperatively when needed
const customGeometry = new BoxGeometry(2, 2, 2)
const customMaterial = new MeshStandardMaterial({
color: 0x00FF00,
metalness: 0.5,
roughness: 0.1
})
const customMesh = new Mesh(customGeometry, customMaterial)
</script>
<template>
<TresCanvas>
<TresPerspectiveCamera :position="[0, 0, 5]" />
<TresAmbientLight />
<TresDirectionalLight :position="[5, 5, 5]" />
<!-- Declarative approach -->
<TresMesh :position="[-2, 0, 0]">
<TresBoxGeometry />
<TresMeshStandardMaterial color="red" />
</TresMesh>
<!-- Imperative approach via primitive -->
<primitive :object="customMesh" :position="[2, 0, 0]" />
</TresCanvas>
</template>
::tip This hybrid approach is particularly useful when integrating existing Three.js code or when you need the full power of Three.js for complex operations. Learn more about this in our Primitives guide. ::
:::card-group ::card{title="Declarative Benefits" icon="i-lucide-lightbulb"} Write what you want, not how to achieve it. Let TresJS handle the complex Three.js operations. ::
::card{title="Reactive by Design" icon="i-lucide-refresh-cw"} Leverage Vue's reactivity system for automatic updates and seamless state management. ::
::card{title="Component-First" icon="i-lucide-layers"} Build reusable 3D components that can be composed and extended like any Vue component. ::
::card{title="Flexible Architecture" icon="i-lucide-settings"} Choose the right approach for each use case - declarative for most scenarios, imperative when needed. :: :::
The declarative approach in TresJS makes 3D development more accessible and maintainable while preserving the full power of Three.js underneath. This paradigm shift allows developers to focus on creating amazing 3D experiences rather than managing complex imperative code.