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.
First, install GSAP as a dependency in your project:
npm install gsap
Import GSAP and the necessary Vue composables. Use shallowRef for better performance with Three.js objects:
import { shallowRef, watch } from 'vue'
import { OrbitControls } from '@tresjs/cientos'
import gsap from 'gsap'
shallowRef instead of ref to avoid unnecessary reactivity on Three.js objects, which improves performance.Set up an array of positions for multiple boxes that will be animated with stagger effects:
const boxesRef = shallowRef()
const zs = []
for (let z = -4.5; z <= 4.5; z++) {
zs.push(z)
}
Create a group of meshes that will be animated together:
<template>
<TresPerspectiveCamera :position="[-15, 10, 15]" />
<OrbitControls />
<TresGroup ref="boxesRef">
<TresMesh v-for="(z, i) of zs" :key="i" :position="[0, 0.5, z]">
<TresBoxGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</TresGroup>
<TresGridHelper :args="[10, 10, 0x444444, 'teal']" />
</template>
Use Vue's watch to set up the animation when the template ref is available:
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,
})
})
The stagger property provides powerful control over timing:
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)
},
}
For more complex sequences, use GSAP timelines to coordinate multiple animations:
<script setup lang="ts">
import { shallowRef, watch, onMounted } from 'vue'
import gsap from 'gsap'
const groupRef = shallowRef()
const timeline = gsap.timeline({ paused: true })
watch(groupRef, () => {
if (!groupRef.value) return
const children = Array.from(groupRef.value.children)
// Clear existing timeline
timeline.clear()
// Add multiple animations to timeline
timeline
.to(children.map(child => child.position), {
y: 3,
duration: 1,
ease: 'back.out(1.7)',
stagger: 0.1
})
.to(children.map(child => child.rotation), {
y: Math.PI * 2,
duration: 2,
ease: 'power2.inOut',
stagger: 0.1
}, '-=0.5') // Start 0.5s before previous animation ends
.to(children.map(child => child.scale), {
x: 1.5,
y: 1.5,
z: 1.5,
duration: 0.5,
ease: 'elastic.out(1, 0.3)',
stagger: 0.05
})
})
// Control functions
const playAnimation = () => timeline.play()
const pauseAnimation = () => timeline.pause()
const reverseAnimation = () => timeline.reverse()
const restartAnimation = () => timeline.restart()
</script>
When animating many objects, optimize performance by:
shallowRef for Three.js object referencesset() method for immediate property changesforce3D: true// 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'
})
}
GSAP provides powerful callback events to sync with your application state:
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')
})