[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7CEGF75MPCMTHKEAW505FE4":3,"skill-same:01M7CEGF75MPCMTHKEAW505FE4":49},{"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":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":26,"commitSha":27,"skillDirectory":28,"forksCount":29,"repositoryHtmlUrl":30,"repositoryHomepageUrl":31,"ownerHtmlUrl":32,"ownerBlogUrl":6,"ownerDescription":33,"updatedAt":34,"install":35,"resources":48},"01M7CEGF75MPCMTHKEAW505FE4","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.","YYC-Cube\u002FYYC3-Knowledge-Base","YYC-Cube","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F235006543?v=4","01M79J2PH4SWD2X0825X6PN748","yyc3","01M79J2PHVK3F6ZBYT8K2SCZCW",1,"MIT","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Fthreejs-animation\u002FSKILL.md",[],[],"2026-11-15 14:19:17",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- 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\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","# Three.js Animation\n\n## When to Use\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\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n",{"name":5,"risk":24,"source":25,"description":7},"unknown","community","main","87afe6ce96220accf50a345dea46784c8c9f210e","YYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Fthreejs-animation",0,"https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base","https:\u002F\u002Fkb.yyc3.top\u002F","https:\u002F\u002Fgithub.com\u002FYYC-Cube","万象归元于云枢 | 深栈智启新纪元","2026-11-10 10:37:32",{"source":8,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill threejs-animation",{"tool":41,"command":42},"bunx","bunx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill threejs-animation",{"tool":44,"command":45},"pnpm","pnpm dlx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill threejs-animation","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Fthreejs-animation\u002FSKILL.md",true,[],[50,57,64,71,78,85],{"skillNo":51,"name":52,"nameZh":6,"description":53,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":54,"categories":55,"keywords":56,"publishedAt":19},"01M7CTDWHERCXPQCP0K03A86AS","vercel-sandbox","Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include \"Vercel Sandbox browser\", \"microVM Chrome\", \"agent-browser in sandbox\", \"browser automation on Vercel\", or any task requiring Chrome in a Vercel Sandbox.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fvercel-sandbox\u002FSKILL.md",[],[],{"skillNo":58,"name":59,"nameZh":6,"description":60,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":61,"categories":62,"keywords":63,"publishedAt":19},"01M7CTD8D2BNJY54S65M47HEJA","slack","Interact with Slack workspaces using browser automation. Use when the user needs to check unread channels, navigate Slack, send messages, extract data, find information, search conversations, or automate any Slack task. Triggers include \"check my Slack\", \"what channels have unreads\", \"send a message to\", \"search Slack for\", \"extract from Slack\", \"find who said\", or any task requiring programmatic Slack interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fslack\u002FSKILL.md",[],[],{"skillNo":65,"name":66,"nameZh":6,"description":67,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":68,"categories":69,"keywords":70,"publishedAt":19},"01M7CTCSSB8ZP3Q58C3YZDZK1W","electron","Automate Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify, etc.) using agent-browser via Chrome DevTools Protocol. Use when the user needs to interact with an Electron app, automate a desktop app, connect to a running app, control a native app, or test an Electron application. Triggers include \"automate Slack app\", \"control VS Code\", \"interact with Discord app\", \"test this Electron app\", \"connect to desktop app\", or any task requiring automation of a native Electron application.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Felectron\u002FSKILL.md",[],[],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":77,"publishedAt":19},"01M7CTCBJY76688V4DCZGWFHGQ","dogfood","Systematically explore and test a web application to find bugs, UX issues, and other problems. Use when asked to \"dogfood\", \"QA\", \"exploratory test\", \"find issues\", \"bug hunt\", \"test this app\u002Fsite\u002Fplatform\", or review the quality of a web application. Produces a structured report with full reproduction evidence -- step-by-step screenshots, repro videos, and detailed repro steps for every issue -- so findings can be handed directly to the responsible teams.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fdogfood\u002FSKILL.md",[],[],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":19},"01M7CTC4H0FFGVEAQR9PA4GA1A","agent-browser","Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fagent-browser\u002FSKILL.md",[],[],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":19},"01M7CTBKVFYBG78FDSABSDJTTQ","shadcn","Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn\u002Fui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", \"switch to --preset\", and for building a design system or applying a DESIGN.md or brand spec to shadcn\u002Fui.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-UI-界面组件\u002FYYC3-UI-界面组件-ui\u002Fskills\u002Fshadcn\u002FSKILL.md",[],[]]