Animation in Three.js using Tween.js with examples

There’s not very many articles explaining animation in Three.js and not many examples so I thought I’d give it a go. In this article I’ll help you animate objects position, rotation or scale, fade objects in and out, and tween custom variables in the render loop.

Why Tween.js?

Tween.js animates within the Three.js render loop. This improves overall performance of your WebGL application and helps keep a high frame rate.

It’s also good practice to never create new variables outside your init function (or however you initialise your project). Try to create all your target positions/rotations/scales at the start and reference them later on.

Another thing aim for is to not modify your variables outside the render loop. For example, if the user triggers an event to rotate a mesh 90 degrees, set a variable which the mesh will update to within the render loop. An alternative solution could be to use the below animation function with a duration of 0 seconds.

To use the examples below you’ll need to include and setup Tween.js in your project which you can find here: https://github.com/tweenjs/tween.js/


Examples

Animating a THREE.Vector3

Animating a Vector3 isn’t that hard since you can pass the Vector3 to a function and modify the x, y and z values directly. This function works nicely when you want to animate the position, rotation and/or scale of an object.

To use this function in an existing project just replace the variable “mesh” with your own object/mesh and pass the function the position, rotation or scale property.

/* Animates a Vector3 to the target */
function animateVector3(vectorToAnimate, target, options){
    options = options || {};
    // get targets from options or set to defaults
var to = target || THREE.Vector3(),
easing = options.easing || TWEEN.Easing.Quadratic.In,
duration = options.duration || 2000;
    // create the tween
var tweenVector3 = new TWEEN.Tween(vectorToAnimate)
.to({ x: to.x, y: to.y, z: to.z, }, duration)
.easing(easing)
.onUpdate(function(d) {
if(options.update){
options.update(d);
}
})
.onComplete(function(){
if(options.callback) options.callback();
});
    // start the tween
tweenVector3.start();
    // return the tween in case we want to manipulate it later on
return tweenVector3;
}
/* How to use */
var target = new THREE.Vector3(10, -20, 20); // create on init
animateVector3(mesh.position, target, {

duration: 5000,

easing : TWEEN.Easing.Quadratic.InOut,

update: function(d) {
console.log("Updating: " + d);
},

callback : function(){
console.log("Completed");
}
});

Fading objects in and out

Fading objects in and out is a bit trickier. This requires us to animate the opacity of an object’s material/s.

We also need to consider that the material’s opacity value might not always be at 1. For example, what if we have a window model that consists of 5 materials but the glass material has an original opacity of .4. For these cases we need to track the original opacities for each object.

To track the original opacities we’ll use the userData property on our mesh. This a good place to store custom data about our objects that we want to use later on in our project.

/* Track original opacities */
function trackOriginalOpacities(mesh) {

var opacities = [],
materials = mesh.material.materials ? mesh.material.materials : [mesh.material];
    for (var i = 0; i < materials.length; i++) {        
         materials[i].transparent = true;
opacities.push(materials[i].opacity);
    }
    mesh.userData.originalOpacities = opacities;
}

Next we want to tween the opacity of our objects material at a percentage between it’s original opacity and zero. The opacity will be applied in the the tween’s update function as a percentage between 0 and it’s original opacity.

/* Fade mesh */
function fadeMesh(mesh, direction, options) {
    options = options || {};
    // set and check 
var current = { percentage : direction == "in" ? 1 : 0 },
    // this check is used to work with normal and multi materials.
mats = mesh.material.materials ?
mesh.material.materials : [mesh.material],

originals = mesh.userData.originalOpacities,
easing = options.easing || TWEEN.Easing.Linear.None,
duration = options.duration || 2000;
    // check to make sure originals exist
if( !originals ) {
console.error("Fade error: originalOpacities not defined, use trackOriginalOpacities");
return;
}
    // tween opacity back to originals
var tweenOpacity = new TWEEN.Tween(current)
.to({ percentage: direction == "in" ? 0 : 1 }, duration)
.easing(easing)
.onUpdate(function() {
for (var i = 0; i < mats.length; i++) {
mats[i].opacity = originals[i] * current.percentage;
}
})
.onComplete(function(){
if(options.callback){
options.callback();
}
});
    tweenOpacity.start();
    return tweenOpacity;
}
/* How to use */
// fade in 
fadeMesh(mesh, "in");
// fade out 
fadeMesh(mesh, "out");
// fade with options
fadeMesh(mesh, "in", {

duration: 11000,

easing: TWEEN.Easing.Quintic.InOut,

callback : function (){
console.log("Fade complete");
}
});

Tweening custom variables

You might find yourself in some cases where you need to animate something out of the ordinary but don’t want to write a custom animation function for it every time. In these situations we’ll pass the object and the variable that we want to manipulate.

/* Tweening a custom variable */
function tweenCustomVar(obj, target, options) {
    options = options || {};
    var easing = options.easing || TWEEN.Easing.Linear.None,
duration = options.duration || 2000,
variable = options.variable || 'opacity',
tweenTo = {};
    tweenTo[variable] = target; // set the custom variable to the target
    var tween = new TWEEN.Tween(obj)
.to(tweenTo, duration)
.easing(easing)
.onUpdate(function(d) {
if(options.update){
options.update(d);
}
})
.onComplete(function(){
if(options.callback) {
options.callback();
}
});
    tween.start();
    return tween;
}
/* How to use */
tweenCustomVar(obj, 100, {
    variable : 'loadingPercentage', // the variable we want to tween
    duration: 1000, 
    easing : TWEEN.Easing.Exponential.In,
    update: function(d) {
console.log("Updating: " + d);
},

callback : function(){
console.log("Completed");
}
});

Where can you see these examples in action?

I learned all of these by working on the Qantas Dreamliner website and thought I’d pass on some knowledge. Check it out and let me know what you think!