analyze
FreeRisk analysis and approach selection before planning. Use when requirements are ambiguous, approaches vary, or work touches hub/bridge nodes. Skip for typo fixes.
The English summary is being prepared. Showing the original description.
Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations.
The English summary is being prepared. Showing the original description.
See how to installReview the instructions the agent will receive before you install.
import * as THREE from "three";
// Simple procedural animation
const clock = new THREE.Clock();
function animate() {
const delta = clock.getDelta();
const elapsed = clock.getElapsedTime();
mesh.rotation.y += delta;
mesh.position.y = Math.sin(elapsed) * 0.5;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
Three.js animation system has three main components:
Stores keyframe animation data.
// Create animation clip
const times = [0, 1, 2]; // Keyframe times (seconds)
const values = [0, 1, 0]; // Values at each keyframe
const track = new THREE.NumberKeyframeTrack(
".position[y]", // Property path
times,
values,
);
const clip = new THREE.AnimationClip("bounce", 2, [track]);
// Number track (single value)
new THREE.NumberKeyframeTrack(".opacity", times, [1, 0]);
new THREE.NumberKeyframeTrack(".material.opacity", times, [1, 0]);
// Vector track (position, scale)
new THREE.VectorKeyframeTrack(".position", times, [
0,
0,
0, // t=0
1,
2,
0, // t=1
0,
0,
0, // t=2
]);
// Quaternion track (rotation)
const q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));
const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0));
new THREE.QuaternionKeyframeTrack(
".quaternion",
[0, 1],
[q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w],
);
// Color track
new THREE.ColorKeyframeTrack(".material.color", times, [
1,
0,
0, // red
0,
1,
0, // green
0,
0,
1, // blue
]);
// Boolean track
new THREE.BooleanKeyframeTrack(".visible", [0, 0.5, 1], [true, false, true]);
// String track (for morph targets)
new THREE.StringKeyframeTrack(
".morphTargetInfluences[smile]",
[0, 1],
["0", "1"],
);
const track = new THREE.VectorKeyframeTrack(".position", times, values);
// Interpolation
track.setInterpolation(THREE.InterpolateLinear); // Default
track.setInterpolation(THREE.InterpolateSmooth); // Cubic spline
track.setInterpolation(THREE.InterpolateDiscrete); // Step function
Plays animations on an object and its descendants.
const mixer = new THREE.AnimationMixer(model);
// Create action from clip
const action = mixer.clipAction(clip);
action.play();
// Update in animation loop
function animate() {
const delta = clock.getDelta();
mixer.update(delta); // Required!
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
mixer.addEventListener("finished", (e) => {
console.log("Animation finished:", e.action.getClip().name);
});
mixer.addEventListener("loop", (e) => {
console.log("Animation looped:", e.action.getClip().name);
});
Controls playback of an animation clip.
const action = mixer.clipAction(clip);
// Playback control
action.play();
action.stop();
action.reset();
action.halt(fadeOutDuration);
// Playback state
action.isRunning();
action.isScheduled();
// Time control
action.time = 0.5; // Current time
action.timeScale = 1; // Playback speed (negative = reverse)
action.paused = false;
// Weight (for blending)
action.weight = 1; // 0-1, contribution to final pose
action.setEffectiveWeight(1);
// Loop modes
action.loop = THREE.LoopRepeat; // Default: loop forever
action.loop = THREE.LoopOnce; // Play once and stop
action.loop = THREE.LoopPingPong; // Alternate forward/backward
action.repetitions = 3; // Number of loops (Infinity default)
// Clamping
action.clampWhenFinished = true; // Hold last frame when done
// Blending
action.blendMode = THREE.NormalAnimationBlendMode;
action.blendMode = THREE.AdditiveAnimationBlendMode;
// Fade in
action.reset().fadeIn(0.5).play();
// Fade out
action.fadeOut(0.5);
// Crossfade between animations
const action1 = mixer.clipAction(clip1);
const action2 = mixer.clipAction(clip2);
action1.play();
// Later, crossfade to action2
action1.crossFadeTo(action2, 0.5, true);
action2.play();
Most common source of skeletal animations.
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
loader.load("model.glb", (gltf) => {
const model = gltf.scene;
scene.add(model);
// Create mixer
const mixer = new THREE.AnimationMixer(model);
// Get all clips
const clips = gltf.animations;
console.log(
"Available animations:",
clips.map((c) => c.name),
);
// Play first animation
if (clips.length > 0) {
const action = mixer.clipAction(clips[0]);
action.play();
}
// Play specific animation by name
const walkClip = THREE.AnimationClip.findByName(clips, "Walk");
if (walkClip) {
mixer.clipAction(walkClip).play();
}
// Store mixer for update loop
window.mixer = mixer;
});
// Animation loop
function animate() {
const delta = clock.getDelta();
if (window.mixer) window.mixer.update(delta);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
// Access skeleton from skinned mesh
const skinnedMesh = model.getObjectByProperty("type", "SkinnedMesh");
const skeleton = skinnedMesh.skeleton;
// Access bones
skeleton.bones.forEach((bone) => {
console.log(bone.name, bone.position, bone.rotation);
});
// Find specific bone by name
const headBone = skeleton.bones.find((b) => b.name === "Head");
if (headBone) headBone.rotation.y = Math.PI / 4; // Turn head
// Skeleton helper
const helper = new THREE.SkeletonHelper(model);
scene.add(helper);
function animate() {
const time = clock.getElapsedTime();
// Animate bone
const headBone = skeleton.bones.find((b) => b.name === "Head");
if (headBone) {
headBone.rotation.y = Math.sin(time) * 0.3;
}
// Update mixer if also playing clips
mixer.update(clock.getDelta());
}
// Attach object to bone
const weapon = new THREE.Mesh(weaponGeometry, weaponMaterial);
const handBone = skeleton.bones.find((b) => b.name === "RightHand");
if (handBone) handBone.add(weapon);
// Offset attachment
weapon.position.set(0, 0, 0.5);
weapon.rotation.set(0, Math.PI / 2, 0);
Blend between different mesh shapes.
// Morph targets are stored in geometry
const geometry = mesh.geometry;
console.log("Morph attributes:", Object.keys(geometry.morphAttributes));
// Access morph target influences
mesh.morphTargetInfluences; // Array of weights
mesh.morphTargetDictionary; // Name -> index mapping
// Set morph target by index
mesh.morphTargetInfluences[0] = 0.5;
// Set by name
const smileIndex = mesh.morphTargetDictionary["smile"];
mesh.morphTargetInfluences[smileIndex] = 1;
// Procedural
function animate() {
const t = clock.getElapsedTime();
mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) / 2;
}
// With keyframe animation
const track = new THREE.NumberKeyframeTrack(
".morphTargetInfluences[smile]",
[0, 0.5, 1],
[0, 1, 0],
);
const clip = new THREE.AnimationClip("smile", 1, [track]);
mixer.clipAction(clip).play();
Mix multiple animations together.
// Setup actions
const idleAction = mixer.clipAction(idleClip);
const walkAction = mixer.clipAction(walkClip);
const runAction = mixer.clipAction(runClip);
// Play all with different weights
idleAction.play();
walkAction.play();
runAction.play();
// Set initial weights
idleAction.setEffectiveWeight(1);
walkAction.setEffectiveWeight(0);
runAction.setEffectiveWeight(0);
// Blend based on speed
function updateAnimations(speed) {
if (speed < 0.1) {
idleAction.setEffectiveWeight(1);
walkAction.setEffectiveWeight(0);
runAction.setEffectiveWeight(0);
} else if (speed < 5) {
const t = speed / 5;
idleAction.setEffectiveWeight(1 - t);
walkAction.setEffectiveWeight(t);
runAction.setEffectiveWeight(0);
} else {
const t = Math.min((speed - 5) / 5, 1);
idleAction.setEffectiveWeight(0);
walkAction.setEffectiveWeight(1 - t);
runAction.setEffectiveWeight(t);
}
}
// Base pose
const baseAction = mixer.clipAction(baseClip);
baseAction.play();
// Additive layer (e.g., breathing)
const additiveAction = mixer.clipAction(additiveClip);
additiveAction.blendMode = THREE.AdditiveAnimationBlendMode;
additiveAction.play();
// Convert clip to additive
THREE.AnimationUtils.makeClipAdditive(additiveClip);
import * as THREE from "three";
// Find clip by name
const clip = THREE.AnimationClip.findByName(clips, "Walk");
// Create subclip
const subclip = THREE.AnimationUtils.subclip(clip, "subclip", 0, 30, 30);
// Convert to additive
THREE.AnimationUtils.makeClipAdditive(clip);
THREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);
// Clone clip
const clone = clip.clone();
// Get clip duration
clip.duration;
// Optimize clip (remove redundant keyframes)
clip.optimize();
// Reset clip to first frame
clip.resetDuration();
// Smooth follow/lerp
const target = new THREE.Vector3();
const current = new THREE.Vector3();
const velocity = new THREE.Vector3();
function smoothDamp(current, target, velocity, smoothTime, deltaTime) {
const omega = 2 / smoothTime;
const x = omega * deltaTime;
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
const change = current.clone().sub(target);
const temp = velocity
.clone()
.add(change.clone().multiplyScalar(omega))
.multiplyScalar(deltaTime);
velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp);
return target.clone().add(change.add(temp).multiplyScalar(exp));
}
function animate() {
current.copy(smoothDamp(current, target, velocity, 0.3, delta));
mesh.position.copy(current);
}
class Spring {
constructor(stiffness = 100, damping = 10) {
this.stiffness = stiffness;
this.damping = damping;
this.position = 0;
this.velocity = 0;
this.target = 0;
}
update(dt) {
const force = -this.stiffness * (this.position - this.target);
const dampingForce = -this.damping * this.velocity;
this.velocity += (force + dampingForce) * dt;
this.position += this.velocity * dt;
return this.position;
}
}
const spring = new Spring(100, 10);
spring.target = 1;
function animate() {
mesh.position.y = spring.update(delta);
}
function animate() {
const t = clock.getElapsedTime();
// Sine wave
mesh.position.y = Math.sin(t * 2) * 0.5;
// Bouncing
mesh.position.y = Math.abs(Math.sin(t * 3)) * 2;
// Circular motion
mesh.position.x = Math.cos(t) * 2;
mesh.position.z = Math.sin(t) * 2;
// Figure 8
mesh.position.x = Math.sin(t) * 2;
mesh.position.z = Math.sin(t * 2) * 1;
}
clip.optimize() to remove redundant keyframes// Pause animation when not visible
mesh.onBeforeRender = () => {
action.paused = false;
};
mesh.onAfterRender = () => {
// Check if will be visible next frame
if (!isInFrustum(mesh)) {
action.paused = true;
}
};
// Cache clips
const clipCache = new Map();
function getClip(name) {
if (!clipCache.has(name)) {
clipCache.set(name, loadClip(name));
}
return clipCache.get(name);
}
threejs-loaders - Loading animated GLTF modelsthreejs-fundamentals - Clock and animation loopthreejs-shaders - Vertex animation in shadersNo reviews yet. Tell others what you thought.
No login needed. Share how it worked for you, ask a question, or note where it did not run.
No comments yet. Be the first to write one.
Risk analysis and approach selection before planning. Use when requirements are ambiguous, approaches vary, or work touches hub/bridge nodes. Skip for typo fixes.
The English summary is being prepared. Showing the original description.
Proactive architecture review — explore codebase structure, generate a self-contained HTML report with Mermaid diagrams and candidate improvements, then grill the findings. Use when planning a large refactor, onboarding to an unfamiliar codebase, or before a major architectural change.
The English summary is being prepared. Showing the original description.
Autonomous human-like browser control and professional QA/QC testing. Powered by agent-browser (Rust native CDP, zero Playwright) with ISTQB test matrix generation, automated testcase execution, console/network error tracing, and defect reporting.
The English summary is being prepared. Showing the original description.
Persistent token-compression mode (~75% reduction) — now always-on by default. Strips filler while keeping code exact.
The English summary is being prepared. Showing the original description.
Database migration best practices for schema changes, data migrations, rollbacks, and zero-downtime deployments across PostgreSQL, MySQL, and common ORMs (Prisma, Drizzle, Kysely, Django, TypeORM, golang-migrate).
The English summary is being prepared. Showing the original description.
Anti-slop frontend skill for landing pages, portfolios, and redesigns. The agent reads the brief, infers the right design direction, and ships interfaces that do not look templated. Real design systems when applicable, audit-first on redesigns, strict pre-flight check.
The English summary is being prepared. Showing the original description.