[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6GD7HVS809C7MQRYQGSJKXF":3,"skill-same:01M6GD7HVS809C7MQRYQGSJKXF":59},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":37,"commitSha":38,"skillDirectory":39,"forksCount":40,"repositoryHtmlUrl":41,"repositoryHomepageUrl":41,"ownerHtmlUrl":42,"ownerBlogUrl":6,"ownerDescription":43,"updatedAt":44,"install":45,"resources":58},"01M6GD7HVS809C7MQRYQGSJKXF","threejs-animation",null,"Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations.","Sam-06060\u002Fjarvis-assistant","Sam-06060","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F208600848?v=4","01M6CRKT7C7FXD21V6HXF293S0","Samson Frank Ganta","01M6CRKTBEKYFD8YX5Z1GT99EE",6,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fthreejs-animation\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30],"Animation","threejs","threejs animation","gltf","js","use","motion","community","objects","three","targets","procedural","2026-11-10 10:12:30",false,"---\nname: threejs-animation\ndescription: Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations.\nrisk: unknown\nsource: community\n---\n\n# Three.js Animation\n\n## When to Use\n\n- You need to animate objects, rigs, morph targets, or imported GLTF animations in Three.js.\n- The task involves mixers, clips, keyframes, procedural motion, or animation blending.\n- You are building motion behavior in a Three.js scene rather than just static rendering.\n\n## Quick Start\n\n```javascript\nimport * as THREE from \"three\";\n\n\u002F\u002F Simple procedural animation with Timer (recommended in r183)\nconst timer = new THREE.Timer();\n\nrenderer.setAnimationLoop(() => {\n  timer.update();\n  const delta = timer.getDelta();\n  const elapsed = timer.getElapsed();\n\n  mesh.rotation.y += delta;\n  mesh.position.y = Math.sin(elapsed) * 0.5;\n\n  renderer.render(scene, camera);\n});\n```\n\n**Note:** `THREE.Timer` is recommended over `THREE.Clock` as of r183. Timer pauses when the page is hidden and has a cleaner API. `THREE.Clock` still works but is considered legacy.\n\n## Animation System Overview\n\nThree.js animation system has three main components:\n\n1. **AnimationClip** - Container for keyframe data\n2. **AnimationMixer** - Plays animations on a root object\n3. **AnimationAction** - Controls playback of a clip\n\n## AnimationClip\n\nStores keyframe animation data.\n\n```javascript\n\u002F\u002F Create animation clip\nconst times = [0, 1, 2]; \u002F\u002F Keyframe times (seconds)\nconst values = [0, 1, 0]; \u002F\u002F Values at each keyframe\n\nconst track = new THREE.NumberKeyframeTrack(\n  \".position[y]\", \u002F\u002F Property path\n  times,\n  values,\n);\n\nconst clip = new THREE.AnimationClip(\"bounce\", 2, [track]);\n```\n\n### KeyframeTrack Types\n\n```javascript\n\u002F\u002F Number track (single value)\nnew THREE.NumberKeyframeTrack(\".opacity\", times, [1, 0]);\nnew THREE.NumberKeyframeTrack(\".material.opacity\", times, [1, 0]);\n\n\u002F\u002F Vector track (position, scale)\nnew THREE.VectorKeyframeTrack(\".position\", times, [\n  0,\n  0,\n  0, \u002F\u002F t=0\n  1,\n  2,\n  0, \u002F\u002F t=1\n  0,\n  0,\n  0, \u002F\u002F t=2\n]);\n\n\u002F\u002F Quaternion track (rotation)\nconst q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));\nconst q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0));\nnew THREE.QuaternionKeyframeTrack(\n  \".quaternion\",\n  [0, 1],\n  [q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w],\n);\n\n\u002F\u002F Color track\nnew THREE.ColorKeyframeTrack(\".material.color\", times, [\n  1,\n  0,\n  0, \u002F\u002F red\n  0,\n  1,\n  0, \u002F\u002F green\n  0,\n  0,\n  1, \u002F\u002F blue\n]);\n\n\u002F\u002F Boolean track\nnew THREE.BooleanKeyframeTrack(\".visible\", [0, 0.5, 1], [true, false, true]);\n\n\u002F\u002F String track (for morph targets)\nnew THREE.StringKeyframeTrack(\n  \".morphTargetInfluences[smile]\",\n  [0, 1],\n  [\"0\", \"1\"],\n);\n```\n\n### Interpolation Modes\n\n```javascript\nconst track = new THREE.VectorKeyframeTrack(\".position\", times, values);\n\n\u002F\u002F Interpolation\ntrack.setInterpolation(THREE.InterpolateLinear); \u002F\u002F Default\ntrack.setInterpolation(THREE.InterpolateSmooth); \u002F\u002F Cubic spline\ntrack.setInterpolation(THREE.InterpolateDiscrete); \u002F\u002F Step function\n```\n\n### BezierInterpolant (r183)\n\nThree.js r183 adds `THREE.BezierInterpolant` for bezier curve interpolation in keyframe tracks, enabling smoother animation curves with tangent control.\n\n## AnimationMixer\n\nPlays animations on an object and its descendants.\n\n```javascript\nconst mixer = new THREE.AnimationMixer(model);\n\n\u002F\u002F Create action from clip\nconst action = mixer.clipAction(clip);\naction.play();\n\n\u002F\u002F Update in animation loop\nfunction animate() {\n  const delta = clock.getDelta();\n  mixer.update(delta); \u002F\u002F Required!\n\n  requestAnimationFrame(animate);\n  renderer.render(scene, camera);\n}\n```\n\n### Mixer Events\n\n```javascript\nmixer.addEventListener(\"finished\", (e) => {\n  console.log(\"Animation finished:\", e.action.getClip().name);\n});\n\nmixer.addEventListener(\"loop\", (e) => {\n  console.log(\"Animation looped:\", e.action.getClip().name);\n});\n```\n\n## AnimationAction\n\nControls playback of an animation clip.\n\n```javascript\nconst action = mixer.clipAction(clip);\n\n\u002F\u002F Playback control\naction.play();\naction.stop();\naction.reset();\naction.halt(fadeOutDuration);\n\n\u002F\u002F Playback state\naction.isRunning();\naction.isScheduled();\n\n\u002F\u002F Time control\naction.time = 0.5; \u002F\u002F Current time\naction.timeScale = 1; \u002F\u002F Playback speed (negative = reverse)\naction.paused = false;\n\n\u002F\u002F Weight (for blending)\naction.weight = 1; \u002F\u002F 0-1, contribution to final pose\naction.setEffectiveWeight(1);\n\n\u002F\u002F Loop modes\naction.loop = THREE.LoopRepeat; \u002F\u002F Default: loop forever\naction.loop = THREE.LoopOnce; \u002F\u002F Play once and stop\naction.loop = THREE.LoopPingPong; \u002F\u002F Alternate forward\u002Fbackward\naction.repetitions = 3; \u002F\u002F Number of loops (Infinity default)\n\n\u002F\u002F Clamping\naction.clampWhenFinished = true; \u002F\u002F Hold last frame when done\n\n\u002F\u002F Blending\naction.blendMode = THREE.NormalAnimationBlendMode;\naction.blendMode = THREE.AdditiveAnimationBlendMode;\n```\n\n### Fade In\u002FOut\n\n```javascript\n\u002F\u002F Fade in\naction.reset().fadeIn(0.5).play();\n\n\u002F\u002F Fade out\naction.fadeOut(0.5);\n\n\u002F\u002F Crossfade between animations\nconst action1 = mixer.clipAction(clip1);\nconst action2 = mixer.clipAction(clip2);\n\naction1.play();\n\n\u002F\u002F Later, crossfade to action2\naction1.crossFadeTo(action2, 0.5, true);\naction2.play();\n```\n\n## Loading GLTF Animations\n\nMost common source of skeletal animations.\n\n```javascript\nimport { GLTFLoader } from \"three\u002Fexamples\u002Fjsm\u002Floaders\u002FGLTFLoader.js\";\n\nconst loader = new GLTFLoader();\nloader.load(\"model.glb\", (gltf) => {\n  const model = gltf.scene;\n  scene.add(model);\n\n  \u002F\u002F Create mixer\n  const mixer = new THREE.AnimationMixer(model);\n\n  \u002F\u002F Get all clips\n  const clips = gltf.animations;\n  console.log(\n    \"Available animations:\",\n    clips.map((c) => c.name),\n  );\n\n  \u002F\u002F Play first animation\n  if (clips.length > 0) {\n    const action = mixer.clipAction(clips[0]);\n    action.play();\n  }\n\n  \u002F\u002F Play specific animation by name\n  const walkClip = THREE.AnimationClip.findByName(clips, \"Walk\");\n  if (walkClip) {\n    mixer.clipAction(walkClip).play();\n  }\n\n  \u002F\u002F Store mixer for update loop\n  window.mixer = mixer;\n});\n\n\u002F\u002F Animation loop\nfunction animate() {\n  const delta = clock.getDelta();\n  if (window.mixer) window.mixer.update(delta);\n\n  requestAnimationFrame(animate);\n  renderer.render(scene, camera);\n}\n```\n\n## Skeletal Animation\n\n### Skeleton and Bones\n\n```javascript\n\u002F\u002F Access skeleton from skinned mesh\nconst skinnedMesh = model.getObjectByProperty(\"type\", \"SkinnedMesh\");\nconst skeleton = skinnedMesh.skeleton;\n\n\u002F\u002F Access bones\nskeleton.bones.forEach((bone) => {\n  console.log(bone.name, bone.position, bone.rotation);\n});\n\n\u002F\u002F Find specific bone by name\nconst headBone = skeleton.bones.find((b) => b.name === \"Head\");\nif (headBone) headBone.rotation.y = Math.PI \u002F 4; \u002F\u002F Turn head\n\n\u002F\u002F Skeleton helper\nconst helper = new THREE.SkeletonHelper(model);\nscene.add(helper);\n```\n\n### Programmatic Bone Animation\n\n```javascript\nfunction animate() {\n  const time = clock.getElapsedTime();\n\n  \u002F\u002F Animate bone\n  const headBone = skeleton.bones.find((b) => b.name === \"Head\");\n  if (headBone) {\n    headBone.rotation.y = Math.sin(time) * 0.3;\n  }\n\n  \u002F\u002F Update mixer if also playing clips\n  mixer.update(clock.getDelta());\n}\n```\n\n### Bone Attachments\n\n```javascript\n\u002F\u002F Attach object to bone\nconst weapon = new THREE.Mesh(weaponGeometry, weaponMaterial);\nconst handBone = skeleton.bones.find((b) => b.name === \"RightHand\");\nif (handBone) handBone.add(weapon);\n\n\u002F\u002F Offset attachment\nweapon.position.set(0, 0, 0.5);\nweapon.rotation.set(0, Math.PI \u002F 2, 0);\n```\n\n## Morph Targets\n\nBlend between different mesh shapes.\n\n```javascript\n\u002F\u002F Morph targets are stored in geometry\nconst geometry = mesh.geometry;\nconsole.log(\"Morph attributes:\", Object.keys(geometry.morphAttributes));\n\n\u002F\u002F Access morph target influences\nmesh.morphTargetInfluences; \u002F\u002F Array of weights\nmesh.morphTargetDictionary; \u002F\u002F Name -> index mapping\n\n\u002F\u002F Set morph target by index\nmesh.morphTargetInfluences[0] = 0.5;\n\n\u002F\u002F Set by name\nconst smileIndex = mesh.morphTargetDictionary[\"smile\"];\nmesh.morphTargetInfluences[smileIndex] = 1;\n```\n\n### Animating Morph Targets\n\n```javascript\n\u002F\u002F Procedural\nfunction animate() {\n  const t = clock.getElapsedTime();\n  mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) \u002F 2;\n}\n\n\u002F\u002F With keyframe animation\nconst track = new THREE.NumberKeyframeTrack(\n  \".morphTargetInfluences[smile]\",\n  [0, 0.5, 1],\n  [0, 1, 0],\n);\nconst clip = new THREE.AnimationClip(\"smile\", 1, [track]);\nmixer.clipAction(clip).play();\n```\n\n## Animation Blending\n\nMix multiple animations together.\n\n```javascript\n\u002F\u002F Setup actions\nconst idleAction = mixer.clipAction(idleClip);\nconst walkAction = mixer.clipAction(walkClip);\nconst runAction = mixer.clipAction(runClip);\n\n\u002F\u002F Play all with different weights\nidleAction.play();\nwalkAction.play();\nrunAction.play();\n\n\u002F\u002F Set initial weights\nidleAction.setEffectiveWeight(1);\nwalkAction.setEffectiveWeight(0);\nrunAction.setEffectiveWeight(0);\n\n\u002F\u002F Blend based on speed\nfunction updateAnimations(speed) {\n  if (speed \u003C 0.1) {\n    idleAction.setEffectiveWeight(1);\n    walkAction.setEffectiveWeight(0);\n    runAction.setEffectiveWeight(0);\n  } else if (speed \u003C 5) {\n    const t = speed \u002F 5;\n    idleAction.setEffectiveWeight(1 - t);\n    walkAction.setEffectiveWeight(t);\n    runAction.setEffectiveWeight(0);\n  } else {\n    const t = Math.min((speed - 5) \u002F 5, 1);\n    idleAction.setEffectiveWeight(0);\n    walkAction.setEffectiveWeight(1 - t);\n    runAction.setEffectiveWeight(t);\n  }\n}\n```\n\n### Additive Blending\n\n```javascript\n\u002F\u002F Base pose\nconst baseAction = mixer.clipAction(baseClip);\nbaseAction.play();\n\n\u002F\u002F Additive layer (e.g., breathing)\nconst additiveAction = mixer.clipAction(additiveClip);\nadditiveAction.blendMode = THREE.AdditiveAnimationBlendMode;\nadditiveAction.play();\n\n\u002F\u002F Convert clip to additive\nTHREE.AnimationUtils.makeClipAdditive(additiveClip);\n```\n\n## Animation Utilities\n\n```javascript\nimport * as THREE from \"three\";\n\n\u002F\u002F Find clip by name\nconst clip = THREE.AnimationClip.findByName(clips, \"Walk\");\n\n\u002F\u002F Create subclip\nconst subclip = THREE.AnimationUtils.subclip(clip, \"subclip\", 0, 30, 30);\n\n\u002F\u002F Convert to additive\nTHREE.AnimationUtils.makeClipAdditive(clip);\nTHREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);\n\n\u002F\u002F Clone clip\nconst clone = clip.clone();\n\n\u002F\u002F Get clip duration\nclip.duration;\n\n\u002F\u002F Optimize clip (remove redundant keyframes)\nclip.optimize();\n\n\u002F\u002F Reset clip to first frame\nclip.resetDuration();\n```\n\n## Procedural Animation Patterns\n\n### Smooth Damping\n\n```javascript\n\u002F\u002F Smooth follow\u002Flerp\nconst target = new THREE.Vector3();\nconst current = new THREE.Vector3();\nconst velocity = new THREE.Vector3();\n\nfunction smoothDamp(current, target, velocity, smoothTime, deltaTime) {\n  const omega = 2 \u002F smoothTime;\n  const x = omega * deltaTime;\n  const exp = 1 \u002F (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n  const change = current.clone().sub(target);\n  const temp = velocity\n    .clone()\n    .add(change.clone().multiplyScalar(omega))\n    .multiplyScalar(deltaTime);\n  velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp);\n  return target.clone().add(change.add(temp).multiplyScalar(exp));\n}\n\nfunction animate() {\n  current.copy(smoothDamp(current, target, velocity, 0.3, delta));\n  mesh.position.copy(current);\n}\n```\n\n### Spring Physics\n\n```javascript\nclass Spring {\n  constructor(stiffness = 100, damping = 10) {\n    this.stiffness = stiffness;\n    this.damping = damping;\n    this.position = 0;\n    this.velocity = 0;\n    this.target = 0;\n  }\n\n  update(dt) {\n    const force = -this.stiffness * (this.position - this.target);\n    const dampingForce = -this.damping * this.velocity;\n    this.velocity += (force + dampingForce) * dt;\n    this.position += this.velocity * dt;\n    return this.position;\n  }\n}\n\nconst spring = new Spring(100, 10);\nspring.target = 1;\n\nfunction animate() {\n  mesh.position.y = spring.update(delta);\n}\n```\n\n### Oscillation\n\n```javascript\nfunction animate() {\n  const t = clock.getElapsedTime();\n\n  \u002F\u002F Sine wave\n  mesh.position.y = Math.sin(t * 2) * 0.5;\n\n  \u002F\u002F Bouncing\n  mesh.position.y = Math.abs(Math.sin(t * 3)) * 2;\n\n  \u002F\u002F Circular motion\n  mesh.position.x = Math.cos(t) * 2;\n  mesh.position.z = Math.sin(t) * 2;\n\n  \u002F\u002F Figure 8\n  mesh.position.x = Math.sin(t) * 2;\n  mesh.position.z = Math.sin(t * 2) * 1;\n}\n```\n\n## Performance Tips\n\n1. **Share clips**: Same AnimationClip can be used on multiple mixers\n2. **Optimize clips**: Call `clip.optimize()` to remove redundant keyframes\n3. **Disable when off-screen**: Stop mixer updates for invisible objects\n4. **Use LOD for animations**: Simpler rigs for distant characters\n5. **Limit active mixers**: Each mixer.update() has a cost\n\n```javascript\n\u002F\u002F Pause animation when not visible\nmesh.onBeforeRender = () => {\n  action.paused = false;\n};\n\nmesh.onAfterRender = () => {\n  \u002F\u002F Check if will be visible next frame\n  if (!isInFrustum(mesh)) {\n    action.paused = true;\n  }\n};\n\n\u002F\u002F Cache clips\nconst clipCache = new Map();\nfunction getClip(name) {\n  if (!clipCache.has(name)) {\n    clipCache.set(name, loadClip(name));\n  }\n  return clipCache.get(name);\n}\n```\n\n## See Also\n\n- `threejs-loaders` - Loading animated GLTF models\n- `threejs-fundamentals` - Clock and animation loop\n- `threejs-shaders` - Vertex animation in shaders\n","# Three.js Animation\n\n## When to Use\n\n- You need to animate objects, rigs, morph targets, or imported GLTF animations in Three.js.\n- The task involves mixers, clips, keyframes, procedural motion, or animation blending.\n- You are building motion behavior in a Three.js scene rather than just static rendering.\n\n## Quick Start\n\n```javascript\nimport * as THREE from \"three\";\n\n\u002F\u002F Simple procedural animation with Timer (recommended in r183)\nconst timer = new THREE.Timer();\n\nrenderer.setAnimationLoop(() => {\n  timer.update();\n  const delta = timer.getDelta();\n  const elapsed = timer.getElapsed();\n\n  mesh.rotation.y += delta;\n  mesh.position.y = Math.sin(elapsed) * 0.5;\n\n  renderer.render(scene, camera);\n});\n```\n\n**Note:** `THREE.Timer` is recommended over `THREE.Clock` as of r183. Timer pauses when the page is hidden and has a cleaner API. `THREE.Clock` still works but is considered legacy.\n\n## Animation System Overview\n\nThree.js animation system has three main components:\n\n1. **AnimationClip** - Container for keyframe data\n2. **AnimationMixer** - Plays animations on a root object\n3. **AnimationAction** - Controls playback of a clip\n\n## AnimationClip\n\nStores keyframe animation data.\n\n```javascript\n\u002F\u002F Create animation clip\nconst times = [0, 1, 2]; \u002F\u002F Keyframe times (seconds)\nconst values = [0, 1, 0]; \u002F\u002F Values at each keyframe\n\nconst track = new THREE.NumberKeyframeTrack(\n  \".position[y]\", \u002F\u002F Property path\n  times,\n  values,\n);\n\nconst clip = new THREE.AnimationClip(\"bounce\", 2, [track]);\n```\n\n### KeyframeTrack Types\n\n```javascript\n\u002F\u002F Number track (single value)\nnew THREE.NumberKeyframeTrack(\".opacity\", times, [1, 0]);\nnew THREE.NumberKeyframeTrack(\".material.opacity\", times, [1, 0]);\n\n\u002F\u002F Vector track (position, scale)\nnew THREE.VectorKeyframeTrack(\".position\", times, [\n  0,\n  0,\n  0, \u002F\u002F t=0\n  1,\n  2,\n  0, \u002F\u002F t=1\n  0,\n  0,\n  0, \u002F\u002F t=2\n]);\n\n\u002F\u002F Quaternion track (rotation)\nconst q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));\nconst q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0));\nnew THREE.QuaternionKeyframeTrack(\n  \".quaternion\",\n  [0, 1],\n  [q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w],\n);\n\n\u002F\u002F Color track\nnew THREE.ColorKeyframeTrack(\".material.color\", times, [\n  1,\n  0,\n  0, \u002F\u002F red\n  0,\n  1,\n  0, \u002F\u002F green\n  0,\n  0,\n  1, \u002F\u002F blue\n]);\n\n\u002F\u002F Boolean track\nnew THREE.BooleanKeyframeTrack(\".visible\", [0, 0.5, 1], [true, false, true]);\n\n\u002F\u002F String track (for morph targets)\nnew THREE.StringKeyframeTrack(\n  \".morphTargetInfluences[smile]\",\n  [0, 1],\n  [\"0\", \"1\"],\n);\n```\n\n### Interpolation Modes\n\n```javascript\nconst track = new THREE.VectorKeyframeTrack(\".position\", times, values);\n\n\u002F\u002F Interpolation\ntrack.setInterpolation(THREE.InterpolateLinear); \u002F\u002F Default\ntrack.setInterpolation(THREE.InterpolateSmooth); \u002F\u002F Cubic spline\ntrack.setInterpolation(THREE.InterpolateDiscrete); \u002F\u002F Step function\n```\n\n### BezierInterpolant (r183)\n\nThree.js r183 adds `THREE.BezierInterpolant` for bezier curve interpolation in keyframe tracks, enabling smoother animation curves with tangent control.\n\n## AnimationMixer\n\nPlays animations on an object and its descendants.\n\n```javascript\nconst mixer = new THREE.AnimationMixer(model);\n\n\u002F\u002F Create action from clip\nconst action = mixer.clipAction(clip);\naction.play();\n\n\u002F\u002F Update in animation loop\nfunction animate() {\n  const delta = clock.getDelta();\n  mixer.update(delta); \u002F\u002F Required!\n\n  requestAnimationFrame(animate);\n  renderer.render(scene, camera);\n}\n```\n\n### Mixer Events\n\n```javascript\nmixer.addEventListener(\"finished\", (e) => {\n  console.log(\"Animation finished:\", e.action.getClip().name);\n});\n\nmixer.addEventListener(\"loop\", (e) => {\n  console.log(\"Animation looped:\", e.action.getClip().name);\n});\n```\n\n## AnimationAction\n\nControls playback of an animation clip.\n\n```javascript\nconst action = mixer.clipAction(clip);\n\n\u002F\u002F Playback control\naction.play();\naction.stop();\naction.reset();\naction.halt(fadeOutDuration);\n\n\u002F\u002F Playback state\naction.isRunning();\naction.isScheduled();\n\n\u002F\u002F Time control\naction.time = 0.5; \u002F\u002F Current time\naction.timeScale = 1; \u002F\u002F Playback speed (negative = reverse)\naction.paused = false;\n\n\u002F\u002F Weight (for blending)\naction.weight = 1; \u002F\u002F 0-1, contribution to final pose\naction.setEffectiveWeight(1);\n\n\u002F\u002F Loop modes\naction.loop = THREE.LoopRepeat; \u002F\u002F Default: loop forever\naction.loop = THREE.LoopOnce; \u002F\u002F Play once and stop\naction.loop = THREE.LoopPingPong; \u002F\u002F Alternate forward\u002Fbackward\naction.repetitions = 3; \u002F\u002F Number of loops (Infinity default)\n\n\u002F\u002F Clamping\naction.clampWhenFinished = true; \u002F\u002F Hold last frame when done\n\n\u002F\u002F Blending\naction.blendMode = THREE.NormalAnimationBlendMode;\naction.blendMode = THREE.AdditiveAnimationBlendMode;\n```\n\n### Fade In\u002FOut\n\n```javascript\n\u002F\u002F Fade in\naction.reset().fadeIn(0.5).play();\n\n\u002F\u002F Fade out\naction.fadeOut(0.5);\n\n\u002F\u002F Crossfade between animations\nconst action1 = mixer.clipAction(clip1);\nconst action2 = mixer.clipAction(clip2);\n\naction1.play();\n\n\u002F\u002F Later, crossfade to action2\naction1.crossFadeTo(action2, 0.5, true);\naction2.play();\n```\n\n## Loading GLTF Animations\n\nMost common source of skeletal animations.\n\n```javascript\nimport { GLTFLoader } from \"three\u002Fexamples\u002Fjsm\u002Floaders\u002FGLTFLoader.js\";\n\nconst loader = new GLTFLoader();\nloader.load(\"model.glb\", (gltf) => {\n  const model = gltf.scene;\n  scene.add(model);\n\n  \u002F\u002F Create mixer\n  const mixer = new THREE.AnimationMixer(model);\n\n  \u002F\u002F Get all clips\n  const clips = gltf.animations;\n  console.log(\n    \"Available animations:\",\n    clips.map((c) => c.name),\n  );\n\n  \u002F\u002F Play first animation\n  if (clips.length > 0) {\n    const action = mixer.clipAction(clips[0]);\n    action.play();\n  }\n\n  \u002F\u002F Play specific animation by name\n  const walkClip = THREE.AnimationClip.findByName(clips, \"Walk\");\n  if (walkClip) {\n    mixer.clipAction(walkClip).play();\n  }\n\n  \u002F\u002F Store mixer for update loop\n  window.mixer = mixer;\n});\n\n\u002F\u002F Animation loop\nfunction animate() {\n  const delta = clock.getDelta();\n  if (window.mixer) window.mixer.update(delta);\n\n  requestAnimationFrame(animate);\n  renderer.render(scene, camera);\n}\n```\n\n## Skeletal Animation\n\n### Skeleton and Bones\n\n```javascript\n\u002F\u002F Access skeleton from skinned mesh\nconst skinnedMesh = model.getObjectByProperty(\"type\", \"SkinnedMesh\");\nconst skeleton = skinnedMesh.skeleton;\n\n\u002F\u002F Access bones\nskeleton.bones.forEach((bone) => {\n  console.log(bone.name, bone.position, bone.rotation);\n});\n\n\u002F\u002F Find specific bone by name\nconst headBone = skeleton.bones.find((b) => b.name === \"Head\");\nif (headBone) headBone.rotation.y = Math.PI \u002F 4; \u002F\u002F Turn head\n\n\u002F\u002F Skeleton helper\nconst helper = new THREE.SkeletonHelper(model);\nscene.add(helper);\n```\n\n### Programmatic Bone Animation\n\n```javascript\nfunction animate() {\n  const time = clock.getElapsedTime();\n\n  \u002F\u002F Animate bone\n  const headBone = skeleton.bones.find((b) => b.name === \"Head\");\n  if (headBone) {\n    headBone.rotation.y = Math.sin(time) * 0.3;\n  }\n\n  \u002F\u002F Update mixer if also playing clips\n  mixer.update(clock.getDelta());\n}\n```\n\n### Bone Attachments\n\n```javascript\n\u002F\u002F Attach object to bone\nconst weapon = new THREE.Mesh(weaponGeometry, weaponMaterial);\nconst handBone = skeleton.bones.find((b) => b.name === \"RightHand\");\nif (handBone) handBone.add(weapon);\n\n\u002F\u002F Offset attachment\nweapon.position.set(0, 0, 0.5);\nweapon.rotation.set(0, Math.PI \u002F 2, 0);\n```\n\n## Morph Targets\n\nBlend between different mesh shapes.\n\n```javascript\n\u002F\u002F Morph targets are stored in geometry\nconst geometry = mesh.geometry;\nconsole.log(\"Morph attributes:\", Object.keys(geometry.morphAttributes));\n\n\u002F\u002F Access morph target influences\nmesh.morphTargetInfluences; \u002F\u002F Array of weights\nmesh.morphTargetDictionary; \u002F\u002F Name -> index mapping\n\n\u002F\u002F Set morph target by index\nmesh.morphTargetInfluences[0] = 0.5;\n\n\u002F\u002F Set by name\nconst smileIndex = mesh.morphTargetDictionary[\"smile\"];\nmesh.morphTargetInfluences[smileIndex] = 1;\n```\n\n### Animating Morph Targets\n\n```javascript\n\u002F\u002F Procedural\nfunction animate() {\n  const t = clock.getElapsedTime();\n  mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) \u002F 2;\n}\n\n\u002F\u002F With keyframe animation\nconst track = new THREE.NumberKeyframeTrack(\n  \".morphTargetInfluences[smile]\",\n  [0, 0.5, 1],\n  [0, 1, 0],\n);\nconst clip = new THREE.AnimationClip(\"smile\", 1, [track]);\nmixer.clipAction(clip).play();\n```\n\n## Animation Blending\n\nMix multiple animations together.\n\n```javascript\n\u002F\u002F Setup actions\nconst idleAction = mixer.clipAction(idleClip);\nconst walkAction = mixer.clipAction(walkClip);\nconst runAction = mixer.clipAction(runClip);\n\n\u002F\u002F Play all with different weights\nidleAction.play();\nwalkAction.play();\nrunAction.play();\n\n\u002F\u002F Set initial weights\nidleAction.setEffectiveWeight(1);\nwalkAction.setEffectiveWeight(0);\nrunAction.setEffectiveWeight(0);\n\n\u002F\u002F Blend based on speed\nfunction updateAnimations(speed) {\n  if (speed \u003C 0.1) {\n    idleAction.setEffectiveWeight(1);\n    walkAction.setEffectiveWeight(0);\n    runAction.setEffectiveWeight(0);\n  } else if (speed \u003C 5) {\n    const t = speed \u002F 5;\n    idleAction.setEffectiveWeight(1 - t);\n    walkAction.setEffectiveWeight(t);\n    runAction.setEffectiveWeight(0);\n  } else {\n    const t = Math.min((speed - 5) \u002F 5, 1);\n    idleAction.setEffectiveWeight(0);\n    walkAction.setEffectiveWeight(1 - t);\n    runAction.setEffectiveWeight(t);\n  }\n}\n```\n\n### Additive Blending\n\n```javascript\n\u002F\u002F Base pose\nconst baseAction = mixer.clipAction(baseClip);\nbaseAction.play();\n\n\u002F\u002F Additive layer (e.g., breathing)\nconst additiveAction = mixer.clipAction(additiveClip);\nadditiveAction.blendMode = THREE.AdditiveAnimationBlendMode;\nadditiveAction.play();\n\n\u002F\u002F Convert clip to additive\nTHREE.AnimationUtils.makeClipAdditive(additiveClip);\n```\n\n## Animation Utilities\n\n```javascript\nimport * as THREE from \"three\";\n\n\u002F\u002F Find clip by name\nconst clip = THREE.AnimationClip.findByName(clips, \"Walk\");\n\n\u002F\u002F Create subclip\nconst subclip = THREE.AnimationUtils.subclip(clip, \"subclip\", 0, 30, 30);\n\n\u002F\u002F Convert to additive\nTHREE.AnimationUtils.makeClipAdditive(clip);\nTHREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);\n\n\u002F\u002F Clone clip\nconst clone = clip.clone();\n\n\u002F\u002F Get clip duration\nclip.duration;\n\n\u002F\u002F Optimize clip (remove redundant keyframes)\nclip.optimize();\n\n\u002F\u002F Reset clip to first frame\nclip.resetDuration();\n```\n\n## Procedural Animation Patterns\n\n### Smooth Damping\n\n```javascript\n\u002F\u002F Smooth follow\u002Flerp\nconst target = new THREE.Vector3();\nconst current = new THREE.Vector3();\nconst velocity = new THREE.Vector3();\n\nfunction smoothDamp(current, target, velocity, smoothTime, deltaTime) {\n  const omega = 2 \u002F smoothTime;\n  const x = omega * deltaTime;\n  const exp = 1 \u002F (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n  const change = current.clone().sub(target);\n  const temp = velocity\n    .clone()\n    .add(change.clone().multiplyScalar(omega))\n    .multiplyScalar(deltaTime);\n  velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp);\n  return target.clone().add(change.add(temp).multiplyScalar(exp));\n}\n\nfunction animate() {\n  current.copy(smoothDamp(current, target, velocity, 0.3, delta));\n  mesh.position.copy(current);\n}\n```\n\n### Spring Physics\n\n```javascript\nclass Spring {\n  constructor(stiffness = 100, damping = 10) {\n    this.stiffness = stiffness;\n    this.damping = damping;\n    this.position = 0;\n    this.velocity = 0;\n    this.target = 0;\n  }\n\n  update(dt) {\n    const force = -this.stiffness * (this.position - this.target);\n    const dampingForce = -this.damping * this.velocity;\n    this.velocity += (force + dampingForce) * dt;\n    this.position += this.velocity * dt;\n    return this.position;\n  }\n}\n\nconst spring = new Spring(100, 10);\nspring.target = 1;\n\nfunction animate() {\n  mesh.position.y = spring.update(delta);\n}\n```\n\n### Oscillation\n\n```javascript\nfunction animate() {\n  const t = clock.getElapsedTime();\n\n  \u002F\u002F Sine wave\n  mesh.position.y = Math.sin(t * 2) * 0.5;\n\n  \u002F\u002F Bouncing\n  mesh.position.y = Math.abs(Math.sin(t * 3)) * 2;\n\n  \u002F\u002F Circular motion\n  mesh.position.x = Math.cos(t) * 2;\n  mesh.position.z = Math.sin(t) * 2;\n\n  \u002F\u002F Figure 8\n  mesh.position.x = Math.sin(t) * 2;\n  mesh.position.z = Math.sin(t * 2) * 1;\n}\n```\n\n## Performance Tips\n\n1. **Share clips**: Same AnimationClip can be used on multiple mixers\n2. **Optimize clips**: Call `clip.optimize()` to remove redundant keyframes\n3. **Disable when off-screen**: Stop mixer updates for invisible objects\n4. **Use LOD for animations**: Simpler rigs for distant characters\n5. **Limit active mixers**: Each mixer.update() has a cost\n\n```javascript\n\u002F\u002F Pause animation when not visible\nmesh.onBeforeRender = () => {\n  action.paused = false;\n};\n\nmesh.onAfterRender = () => {\n  \u002F\u002F Check if will be visible next frame\n  if (!isInFrustum(mesh)) {\n    action.paused = true;\n  }\n};\n\n\u002F\u002F Cache clips\nconst clipCache = new Map();\nfunction getClip(name) {\n  if (!clipCache.has(name)) {\n    clipCache.set(name, loadClip(name));\n  }\n  return clipCache.get(name);\n}\n```\n\n## See Also\n\n- `threejs-loaders` - Loading animated GLTF models\n- `threejs-fundamentals` - Clock and animation loop\n- `threejs-shaders` - Vertex animation in shaders\n",{"name":5,"risk":36,"source":26,"description":7},"unknown","main","a50a3aa6b39fb0e1b7db88bef1bfc9c93606e126","modules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fthreejs-animation",0,"https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant","https:\u002F\u002Fgithub.com\u002FSam-06060","Mobile Application Developer | Kotlin | Flutter, KMP, Swfit, Android,Swift UI | Building at Saar It Resources | Creator of Jarvis (AI Assistant)","2026-06-23 05:13:38",{"source":8,"skillName":5,"commands":46,"prompt":56,"zipAvailable":57},[47,50,53],{"tool":48,"command":49},"npx","npx skills add Sam-06060\u002Fjarvis-assistant --skill threejs-animation",{"tool":51,"command":52},"bunx","bunx skills add Sam-06060\u002Fjarvis-assistant --skill threejs-animation",{"tool":54,"command":55},"pnpm","pnpm dlx skills add Sam-06060\u002Fjarvis-assistant --skill threejs-animation","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fthreejs-animation\u002FSKILL.md",true,[],[60,73,89,116,135,162],{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":70,"publishedAt":31},"01M6GDRVS6FXX3ESH0C9DW01C0","test_skill","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fscratch\u002Ftest_skill\u002FSKILL.md",[65],{"categoryNo":66,"categoryName":67,"taxonomyType":68,"taxonomyTypeDesc":69},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[71,72],"Test Skill","test",{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":31},"01M6GDRQ40HX22MA6XWC31RHHK","zustand-store-ts","Create Zustand stores following established patterns with proper TypeScript types and middleware.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzustand-store-ts\u002FSKILL.md",[],[80,81,82,83,84,85,86,26,87,88],"zustand store ts","zustand","store","with","types","middleware","patterns","create","typescript",{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":98,"publishedAt":31},"01M6GDRKEA0WE670SPFWX5GKCN","zoom-automation","Automate Zoom meeting creation, management, recordings, webinars, and participant tracking via Rube MCP (Composio). Always search tools first for current schemas.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzoom-automation\u002FSKILL.md",[95],{"categoryNo":96,"categoryName":97,"taxonomyType":68,"taxonomyTypeDesc":69},"01JBSEEDCAT000000000000023","Skill与智能体开发",[99,100,101,102,103,104,105,106,107,108,109,110,111,112,26,113,114,115],"automation","zoom automation","zoom","first","Tools","search","MCP","via","meeting","automate","critical","creation","webinars","composio","recordings","management","tracking",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":123,"publishedAt":31},"01M6GDRFS1452PC71JQ38S24HR","zoho-crm-automation","Automate Zoho CRM tasks via Rube MCP (Composio): create\u002Fupdate records, search contacts, manage leads, and convert leads. Always search tools first for current schemas.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzoho-crm-automation\u002FSKILL.md",[122],{"categoryNo":96,"categoryName":97,"taxonomyType":68,"taxonomyTypeDesc":69},[124,99,125,126,127,128,129,26,104,130,112,131,109,108,103,132,87,133,105,134],"crm automation","zoho crm","crm","zoho crm automation","zoho","leads","contacts","records","update","tasks","manage",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":144,"publishedAt":31},"01M6GDRCKQBAVTGHX5XBH26KPZ","zod-validation-expert","Expert in Zod — TypeScript-first schema validation. Covers parsing, custom errors, refinements, type inference, and integration with React Hook Form, Next.js, and tRPC.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzod-validation-expert\u002FSKILL.md",[141],{"categoryNo":142,"categoryName":143,"taxonomyType":68,"taxonomyTypeDesc":69},"01JBSEEDCAT000000000000009","前端开发",[145,146,147,148,149,88,150,151,152,153,154,155,83,156,157,158,159,26,160,161],"expert","zod","zod validation expert","validation","react","safe","parsing","errors","schema validation","react hook form","hook","trpc","form","type","next","schema","Next.js",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":169,"publishedAt":31},"01M6GDR658B08K4MY0RWD4A6EH","zeroize-audit","Detects missing zeroization of sensitive data in source code and identifies zeroization removed by compiler optimizations, with assembly-level analysis, and control-flow verification. Use for auditing C\u002FC++\u002FRust code handling secrets, keys, passwords, or other sensitive data.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzeroize-audit\u002FSKILL.md",[168],{"categoryNo":96,"categoryName":97,"taxonomyType":68,"taxonomyTypeDesc":69},[170,171,172,173,174,175,176,177,26,178,179,180,181,182,183,184,185,186,187,105],"zeroize audit","audit","write","passwords","get","project","c++","activate","assembly","read","Bash","secrets","analysis","data","flow","verification","code","rust"]