This recipe covers how to use <Transition> and Dynamic Vue built-in components.
Let's start with a simple scene.
<script setup>
import { TresCanvas } from '@tresjs/core'
// import our two custom components
import Sphere from './Sphere.vue'
import Box from './Box.vue'
</script>
<template>
<TresCanvas window-size clear-color="#82DBC5">
<TresPerspectiveCamera
:position="[0, 0, 5]"
/>
<TresAmbientLight :intensity="0.5" />
<TresDirectionalLight
:position="[5, 5, 5]"
:intensity="1"
/>
</TresCanvas>
</template>
I'm going to add some simple animation logic just to add a little more life.
<script setup>
import { shallowRef } from 'vue'
import { useLoop } from '@tresjs/core'
// Retrieving the object
const boxRef = shallowRef()
const { onBeforeRender } = useLoop()
onBeforeRender(({ elapsed }) => {
// little animation logic completely optional
if (boxRef.value) {
boxRef.value.rotation.y = elapsed * 0.5
boxRef.value.rotation.x = elapsed * 0.2
}
})
</script>
<template>
<TresMesh ref="boxRef">
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshStandardMaterial color="orange" transparent />
</TresMesh>
</template>
<script setup>
import { shallowRef } from 'vue'
import { useLoop } from '@tresjs/core'
const sphereRef = shallowRef()
const { onBeforeRender } = useLoop()
onBeforeRender(({ elapsed }) => {
if (sphereRef.value) {
// Moving my sphere instead of rotating
sphereRef.value.position.y = Math.sin(elapsed) * 0.5
sphereRef.value.position.x = Math.cos(elapsed) * 0.2
}
})
</script>
<template>
<TresMesh ref="sphereRef">
<TresSphereGeometry :args="[1, 32]" />
<TresMeshStandardMaterial color="orange" transparent />
</TresMesh>
</template>
You can use dynamic components just as you would in Vue.js. There are several ways to do this, but here we’ll follow the official Vue example.
<component :is="meshes[current]" /> to our scene.<script setup>
import { ref } from 'vue'
import { TresCanvas } from '@tresjs/core'
import Sphere from './Sphere.vue'
import Box from './Box.vue'
const current = ref('Box')
const meshes = {
Box,
Sphere,
}
const handleComponents = (component) => {
current.value = component
}
</script>
<template>
<TresCanvas window-size clear-color="#82DBC5">
<TresPerspectiveCamera
:position="[0, 0, 5]"
/>
<!-- Dynamic component -->
<component :is="meshes[current]" />
<TresAmbientLight :intensity="0.5" />
<TresDirectionalLight
:position="[5, 5, 5]"
:intensity="1"
/>
</TresCanvas>
</template>
To be able to switch between components, lets add a floating UI containing buttons to change between the Box and the Sphere
<template>
<div class="floating-container">
<button
:class="{ isActive: meshes[current] === Box }"
@click="handleComponents('Box')"
>
Cube
</button>
<button
:class="{ isActive: meshes[current] === Sphere }"
@click="handleComponents('Sphere')"
>
Sphere
</button>
</div>
</template>
Let's add a little CSS.
<style scoped>
.floating-container {
position: absolute;
top: 0;
left: 50%;
z-index: 10;
background-color: #f7f7f7;
color: #333;
border-radius: 8px;
padding: 0.25rem;
display: flex;
gap: 0.25rem;
transform: translateX(-50%);
button {
padding: 8px 16px;
border: none;
background-color: #4caf50;
color: white;
cursor: pointer;
border-radius: 4px;
font-size: 14px;
}
}
button.isActive {
background-color: #388e3c;
}
</style>
<transition>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 and <Transition> components.
For that, the first step is to wrap our dynamic component in a <Transition>.
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.
<Transition :css="false">
<component :is="meshes[current]" />
</Transition>
Then we can use the provided JS hooks; in this demo, we're going to use @enter and @leave.
<Transition :css="false" @enter="onEnter" @leave="onLeave">
<component :is="meshes[current]" />
</Transition>
In case you haven't installed it already, install GSAP as a dependency in your project:
npm install gsap
yarn add gsap
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.
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 })
}
opacity works here because we set transparent in our materials.<script setup>
import { ref } from 'vue'
import { TresCanvas } from '@tresjs/core'
import { gsap } from 'gsap'
import Sphere from './Sphere.vue'
import Box from './Box.vue'
const current = ref('Box')
const handleComponents = (component) => {
current.value = component
}
function onEnter(el) {
gsap.from(el.material, { duration: 1, opacity: 0 })
}
function onLeave(el) {
gsap.to(el.material, { duration: 0.05, opacity: 0 })
}
const meshes = {
Box,
Sphere,
}
</script>
<template>
<!---->
<SceneWrapper>
<div class="floating-container">
<button :class="{ isActive: meshes[current] === Box }" @click="handleComponents('Box')">Cube</button>
<button :class="{ isActive: meshes[current] === Sphere }" @click="handleComponents('Sphere')">Sphere</button>
</div>
<TresCanvas clear-color="#82DBC5">
<TresPerspectiveCamera
:position="[0, 0, 5]"
/>
<Transition :css="false" @enter="onEnter" @leave="onLeave">
<component :is="meshes[current]" />
</Transition>
<TresAmbientLight :intensity="0.5" />
<TresDirectionalLight
:position="[5, 5, 5]"
:intensity="1"
/>
</TresCanvas>
</SceneWrapper>
</template>
<style scoped>
.floating-container {
position: absolute;
top: 0;
left: 50%;
z-index: 10;
background-color: #f7f7f7;
color: #333;
border-radius: 8px;
padding: 0.25rem;
display: flex;
gap: 0.25rem;
transform: translateX(-50%);
button {
padding: 8px 16px;
border: none;
background-color: #4caf50;
color: white;
cursor: pointer;
border-radius: 4px;
font-size: 14px;
}
}
button.isActive {
background-color: #388e3c;
}
</style>