🎯

threejs-animation

🎯Skill

from toilahuongg/shopify-agents-kit

VibeIndex|
What it does

Enables precise and flexible object animations in Three.js, supporting keyframe, skeletal, and procedural motion with advanced blending and playback control.

πŸ“¦

Part of

toilahuongg/shopify-agents-kit(35 items)

threejs-animation

Installation

npm installInstall npm package
npm install -g shopify-cc-kit
npm installInstall npm package
npm install shopify-cc-kit
πŸ“– Extracted from docs: toilahuongg/shopify-agents-kit
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

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

Overview

# Three.js Animation

Quick Start

```javascript

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.

```javascript

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

```javascript

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

```javascript

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.

```javascript

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

```javascript

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.

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

```javascript

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

More from this repository10

🎯
shopify-polaris-icons🎯Skill

Provides comprehensive guidance and examples for integrating and using Shopify Polaris Icons in commerce applications with accessibility and semantic color tones.

🎯
shopify-polaris-viz🎯Skill

Generates accessible, Shopify Admin-styled data visualizations using Polaris Viz library for React applications.

🎯
shopify-polaris-design🎯Skill

Generates Shopify Polaris-styled React components and design elements for creating consistent and professional e-commerce interfaces.

🎯
threejs-loaders🎯Skill

Loads and manages 3D assets in Three.js, handling GLTF models, textures, and environments with comprehensive loading progress tracking.

🎯
shopify-webhooks🎯Skill

Handles Shopify webhook verification, processing, and registration for real-time event synchronization with secure HMAC authentication.

🎯
brainstorm🎯Skill

Guides users through creative brainstorming by applying structured techniques like SCAMPER and Design Thinking to generate innovative solutions.

🎯
algorithmic-art🎯Skill

Generates algorithmic art philosophies and p5.js code sketches using computational aesthetics, seeded randomness, and generative design principles.

🎯
shopify-extensions🎯Skill

Guides developers through building and integrating Shopify Extensions across Admin, Checkout, Theme, and Post-purchase interfaces using latest CLI and APIs.

🎯
design🎯Skill

Generates design-related tasks and recommendations for Shopify store aesthetics, layout, and visual branding using AI-powered analysis.

🎯
shopify-pos🎯Skill

Enables developers to create custom UI extensions for Shopify Point of Sale (POS), integrating specialized functionality across various retail interface screens.