How to Create an Immersive Experience with Three.js and GSAP
Published on 13 May, 2025 | ~4 min read | Demo
In this tutorial, we’ll continue our journey with GSAP to animate the camera and bring even more immersive experiences to your Three.js apps.
Before we begin, I recommend checking out this article first. It’s not required to follow along, but it might give you some extra insight into the topic.
Getting Ready
First and foremost, make sure you have a Three.js project set up and running. If not, no worries—you can use my Three.js boilerplate to get started.
Below is the code with a few tweaks: the removal of helpers and orbit controls, as well as the loading of the model (you can find the download link in the credits section below).
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(-1.7, 0, 8.7);
camera.lookAt(1.7, 0, 8.7);
const loader = new GLTFLoader();
loader.load('./the_king_s_hall/scene.gltf', function (gltf) {
const model = gltf.scene;
scene.add(model);
});
function animate() {
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
window.addEventListener('resize', function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
Next, let’s install and import GSAP into the project.
npm install gsap
import gsap from 'gsap';
Time to Make the Camera Fly Around
Since we’ll be animating two camera properties—position
and rotation
—we’ll create a separate function for each to keep the code clean and concise.
function moveCamera(x, y, z) {
gsap.to(camera.position, {
x,
y,
z,
duration: 3,
});
}
function rotateCamera(x, y, z) {
gsap.to(camera.rotation, {
x,
y,
z,
duration: 3.2,
});
}
We’ll also need a variable to keep track of the camera’s current stance.
let stance = 0;
The animation consists of moving the camera to five different positions, and at each one, it will rotate to face a specific direction.
I’m referring to each of these as a stance—or more precisely, the state the camera is in after each animation finishes.
That being said, we’ll use a switch
statement with five cases. Each one will rotate and move the camera using specific values.
loader.load('./the_king_s_hall/scene.gltf', function (gltf) {
const model = gltf.scene;
scene.add(model);
window.addEventListener('mouseup', function () {
switch (stance) {
case 0:
moveCamera(-1.8, 1.6, 5);
rotateCamera(0, 0.1, 0);
stance = 1;
break;
case 1:
moveCamera(2.8, 0, 3.6);
rotateCamera(0, -2, 0);
stance = 2;
break;
case 2:
moveCamera(2.5, -0.9, 12.2);
rotateCamera(0.9, 0.6, -0.6);
stance = 3;
break;
case 3:
moveCamera(-2.7, 0.6, 3.7);
rotateCamera(0.6, 1.9, -0.6);
stance = 4;
break;
case 4:
moveCamera(-1.7, 0, 8.7);
rotateCamera(0, 4.7, 0);
stance = 5;
break;
case 5:
moveCamera(0.5, 0.8, 10);
rotateCamera(0.3, 1.65, -0.3);
stance = 0;
}
});
});
Wondering how I came up with these values?
I simply used the FirstPersonControls
to move around and logged the values by listening to the 'mouseup'
event.
Check out the YouTube tutorial in the related section for a clearer explanation.
Wrap Up
And that’s a wrap for this tutorial.
As you can see, you don’t always need a large codebase with complex logic to create a smooth and immersive experience in your 3D applications.
By the way, this is just the beginning. You can easily add more elements to the scene, such as labels and animated text to highlight different parts of the scene at each stance. For more details, check out this article.