Three.js common gotchas
Edge cases to keep in mind when animating Three.js objects.
Rotation order
rotateX / rotateY / rotateZ write to target.rotation.x / .y / .z directly. Three.js applies them in its default Euler order 'XYZ'. If target.rotation.order has been changed, the result will not match the names. Use a Quaternion outside the adapter when you need a different order.
Transparent flag for opacity fades
Animating opacity on a material whose transparent flag is false has no visible effect. Three.js still renders it fully opaque. Set material.transparent = true on any material intended to fade.
Visibility flip and direct mutation
Setting opacity = 0, scale = 0, or any scale axis to 0 flips target.visible to false. The flip only fires through animate() or utils.set(). A direct write like mesh.scale.x = 0 leaves mesh.visible alone.
Shared materials and the mesh shorthand
Material properties live on the material, not on the mesh. Animating them updates every other mesh sharing that material instance. Clone the material per mesh to scope the animation. The mesh shorthand also only routes to the material when the mesh has no own field with the same name, so a mesh-level property always wins.
Group fades
A Group doesn't own a material, so animating opacity or color on a group has no effect on its children. Pass an array of descendant meshes as targets to fade an entire subtree.
Single Three.js instance
Three.js is an optional peer dependency. Your bundler must resolve 'three' to a single instance. Duplicated copies in nested node_modules make instanceof checks fail, and the adapter silently misses any targets coming from the other copy.
Out-of-scope uniform types
Uniforms whose value is a Texture, Matrix3, or Matrix4, plus UniformArrayNode and BufferNode, are not handled. Animate Texture transforms on the texture itself, or animate the underlying numeric fields directly.
Instance proxy caveats
On a per-instance proxy, opacity writes the shared material so every slot is affected, and visible is a no-op on InstancedMesh. After getInstances() runs, reading back mesh.onBeforeRender will not return the exact function you assigned, though assigning to it still works.