Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    cloudai-x

    threejs-animation

    cloudai-x/threejs-animation
    Design
    1,312
    3 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations.

    SKILL.md

    Three.js Animation

    Quick Start

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

    Animation System Overview

    Three.js animation system has three main components:

    1. AnimationClip - Container for keyframe data
    2. AnimationMixer - Plays animations on a root object
    3. AnimationAction - Controls playback of a clip

    AnimationClip

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

    KeyframeTrack Types

    // 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"],
    );
    

    Interpolation Modes

    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
    

    AnimationMixer

    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 Events

    mixer.addEventListener("finished", (e) => {
      console.log("Animation finished:", e.action.getClip().name);
    });
    
    mixer.addEventListener("loop", (e) => {
      console.log("Animation looped:", e.action.getClip().name);
    });
    

    AnimationAction

    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/Out

    // 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();
    

    Loading GLTF Animations

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

    Skeletal Animation

    Skeleton and Bones

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

    Programmatic Bone Animation

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

    Bone Attachments

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

    Morph Targets

    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;
    

    Animating Morph Targets

    // 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();
    

    Animation Blending

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

    Additive Blending

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

    Animation Utilities

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

    Procedural Animation Patterns

    Smooth Damping

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

    Spring Physics

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

    Oscillation

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

    Performance Tips

    1. Share clips: Same AnimationClip can be used on multiple mixers
    2. Optimize clips: Call clip.optimize() to remove redundant keyframes
    3. Disable when off-screen: Stop mixer updates for invisible objects
    4. Use LOD for animations: Simpler rigs for distant characters
    5. Limit active mixers: Each mixer.update() has a cost
    // 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);
    }
    

    See Also

    • threejs-loaders - Loading animated GLTF models
    • threejs-fundamentals - Clock and animation loop
    • threejs-shaders - Vertex animation in shaders
    Recommended Servers
    Svelte
    Svelte
    Excalidraw
    Excalidraw
    InstantDB
    InstantDB
    Repository
    cloudai-x/threejs-skills
    Files