Three.js adapter NEW
Animate Three.js objects directly with animate(), createTimeline(), and utils.set().
Three.js animations typically require targeting multiple objects: mesh.position, mesh.rotation, mesh.material, and each uniform's value. Keeping them in sync usually involves a timeline. The Three.js adapter flattens these nested fields onto property names on the mesh, converts angles to degrees, parses CSS color values, and reduces the setup to a single animate() call.
Without the adapter:
createTimeline({
defaults: {
duration: 500,
ease: 'inOutSine',
}
})
.add(mesh.position, {
x: 100,
y: 50,
}, 0)
.add(mesh.rotation, {
x: utils.degToRad(30),
y: utils.degToRad(60),
}, 0)
.add(mesh.material, {
opacity: 0.5,
}, 0)
.add(mesh.material.uniforms.uTint.value, {
r: 0,
g: 0.5,
b: 1,
}, 0);
With the adapter:
animate(mesh, {
x: 100, // mesh.position.x
y: 50, // mesh.position.y
rotateX: 30, // mesh.rotation.x (degrees)
rotateY: 60, // mesh.rotation.y (degrees)
opacity: 0.5, // mesh.material.opacity
uTint: '#0080ff', // mesh.material.uniforms.uTint.value
duration: 500,
ease: 'inOutSine',
});
Loading the Three.js adapter
Load the adapter as a side-effect import from the 'animejs/adapters/three' subpath module:
import 'animejs/adapters/three';
Three.js is an optional peer dependency. The adapter works with any version from 0.150.0.
Using the adapter
Once imported, Three.js objects can be passed directly to animate() and utils.set(). Common names like x, rotateY, scale, opacity, and color map to the corresponding field, and any other Color, Vector2/3/4, scalar, or boolean property on the target is detected automatically. Angle-named scalar fields such as rotation and angle are auto-converted between degrees and radians, so all rotation values are written and read in degrees.
Supported targets: Object3D and its subclasses (Mesh, lights, cameras, Sprite, Points, ...), Material, Texture, Fog / FogExp2, TSL UniformNode, and bare Color / Vector2-4 instances.
animate(mesh, { x: 100, rotateY: 360, opacity: 0.5 });
animate(camera, { fov: 60, focalLength: 35 });
animate(light, { color: '#fffacd' });
animate(scene, { background: '#1a1a2e' });
animate(texture, { offsetX: 1, rotation: Math.PI });
See Object properties for the full reference.
Color
Color fields like material.color, material.emissive, light.color, fog.color, or scene.background accept any color value, including CSS variables.
animate(material, { color: '#ff8800', emissive: '#0ff' });
animate(scene, { background: 'rgb(20, 30, 40)' });
animate(light, { color: 'hsl(200, 80%, 50%)' });
animate(material, { color: 'var(--accent)' });
Vectors
Vector2, Vector3, and Vector4 fields are split into per-axis names by appending X, Y, Z, or W.
// material.normalScale.x / material.normalScale.y
animate(material, { normalScaleX: 0.5, normalScaleY: 1 });
// sprite.center.x
animate(sprite, { centerX: 0.25 });
// texture.offset.x / texture.offset.y
animate(texture, { offsetX: 1, offsetY: 0.5 });
Shader uniforms and TSL slots
Uniforms on a ShaderMaterial and slots assigned to a UniformNode from 'three/tsl' are exposed by name on the material, and on the parent mesh through the material shorthand. Color and Vector uniforms use the same naming rules as material fields.
animate(shaderMaterial, { uTime: 1, uTint: '#0ff', uOffsetY: 0.5 });
animate(mesh, { uTime: 1, uTint: '#0ff' });
See Materials and uniforms for the full reference.
Three.js adapter code example
import { animate, createTimer, stagger, utils } from 'animejs';
import * as THREE from 'three';
import { getInstances } from 'animejs/adapters/three';
// Three.js setup
const [ $container ] = utils.$('.full-container');
const color = utils.get($container, 'color');
const { width, height } = $container.getBoundingClientRect();
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
renderer.shadowMap.enabled = true;
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
$container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 100);
camera.position.z = 6;
scene.add(camera);
scene.add(new THREE.AmbientLight(0xffffff, 0.25));
const pointLight = new THREE.PointLight(0xffffff, 8, 20, 0.4);
pointLight.castShadow = true;
scene.add(pointLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(2, 3, 4);
scene.add(dirLight);
const gridSize = 4; // cubes per axis
const cellSize = 2 / gridSize; // size of each cube
const spread = (gridSize - 1) / 2 * cellSize; // distance from center to the outer cubes
const geometry = new THREE.BoxGeometry(cellSize, cellSize, cellSize);
const material = new THREE.MeshLambertMaterial({ color });
const mesh = new THREE.InstancedMesh(geometry, material, gridSize * gridSize * gridSize);
mesh.castShadow = mesh.receiveShadow = true;
scene.add(mesh);
// Animation with Three.js adapter
const instances = getInstances(mesh);
utils.set(instances, {
x: stagger([-spread, spread], { grid: [gridSize, gridSize, gridSize], axis: 'x' }),
y: stagger([-spread, spread], { grid: [gridSize, gridSize, gridSize], axis: 'y' }),
z: stagger([-spread, spread], { grid: [gridSize, gridSize, gridSize], axis: 'z' }),
});
animate(mesh, {
rotateY: { to: 360, duration: 9000 },
rotateX: { to: 360, duration: 12000 },
loop: true,
ease: 'inOutQuad',
});
animate(pointLight, {
intensity: [30, 0],
duration: 2500,
loop: true,
loopDelay: 500,
alternate: true,
ease: 'out(3)',
});
animate(instances, {
x: (instance) => instance.x * 10,
y: (instance) => instance.y * 10,
z: (instance) => instance.z * 10,
duration: 2000,
delay: stagger([0, 500], { grid: true, from: 'center', reversed: true, ease: 'in(3)' }),
loop: true,
loopDelay: 500,
alternate: true,
ease: 'inOutExpo',
});
createTimer({ onUpdate: () => renderer.render(scene, camera) });
<div class="full-container"></div>