Creating a Smooth Camera Zoom Effect in Three.js

Published on 19 Nov, 2024 | ~3 min read | Demo

Creating a magnificent 3D world with a Three.js scene is just the beginning. To deliver a perfect user experience, the act of exploring it must be as smooth and enjoyable as possible.

That being said, as trivial as it may seem, a small tweak to the camera controls can make a huge difference.

OrbitControls Damping

First and foremost, I assume you already have a Three.js project up and running. If not, feel free to use my Three.js boilerplate.

import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';

const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Sets the color of the background.
renderer.setClearColor(0xFEFEFE);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);

// Sets orbit control to move the camera around.
const orbit = new OrbitControls(camera, renderer.domElement);

// Camera positioning.
camera.position.set(6, 8, 14);
// Has to be done everytime we update the camera position.
orbit.update();

// Creates a 12 by 12 grid helper.
const gridHelper = new THREE.GridHelper(12, 12);
scene.add(gridHelper);

// Creates an axes helper with an axis length of 4.
const axesHelper = new THREE.AxesHelper(4);
scene.add(axesHelper);

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);
});

As you can see, we have some basic setup, including OrbitControls. While this allows us to look around the scene, the camera movement feels somewhat rigid, if I may say so.

That said, we can smooth out the camera movement by enabling the enableDamping property.

orbit.enableDamping = true;

When you enable damping, you must update the controls in the animate() function. Otherwise, the camera will freeze whenever you try to rotate or pan.

function animate() {
  orbit.update();

  renderer.render(scene, camera);
}

Live Demo.

In addition, we can fine-tune the smoothness and inertia of the movement using the dampingFactor property.

orbit.dampingFactor = 0.12;

Damping Doesn't Affect the Zoom

That being said, the bad news is that the smooth zooming effect isn't possible with OrbitControls, at least not in the current version of the module.

The good news, on the other hand, is that we can still easily achieve smooth zooming by following a couple of simple steps.

The main idea of what we're going to do is simply borrow the zoom damping from the TrackballControls module.

The first step is to import the TrackballControls module.

import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';

Next, we'll disable the zoom in the OrbitControls since we'll be letting the TrackballControls handle that.

orbit.enableZoom = false;

Then, we'll create an instance of TrackballControls, disable the rotation and panning, and activate only the zoom.

const controls2 = new TrackballControls(camera, renderer.domElement);
controls2.noRotate = true;
controls2.noPan = true;
controls2.noZoom = false;
controls2.zoomSpeed = 1.5;

Finally, in the animate() function, we'll match the target of both controls.

function animate() {
  const target = orbit.target;
  controls2.target.set(target.x, target.y, target.z);

  orbit.update();
  controls2.update();

  renderer.render(scene, camera);
}

Wrap Up

Smoothing the zoom is a great tweak to the camera controls to enhance the user experience, but it's definitely not the only one. There are many more tricks and advanced techniques, especially when combined with third-party libraries like GSAP and Tween.js.

Happy coding!

Buy me a coffee

Credits and Resources

Related Content