Navigate
Search topics across all sections
Position, Rotation & Scale
Every object in 3D space has three fundamental transforms: where it is (position), which way it faces (rotation), and how big it is (scale). Mastering these three properties is the foundation of all 3D work — from placing a button in a UI to choreographing a cinematic camera move.
You set rotation={[90, 0, 0]} expecting a nice 90-degree tilt. Instead, the object spins wildly because Three.js uses radians, not degrees. 90 radians is about 14 full rotations. Your mesh is facing some random direction and you have no idea why.
Mesh rotated to unexpected angle. rotation={[90, 0, 0]} but object is not at 90 degrees.Real-world
Think of transforms as dance choreography.
Position is where the dancer stands on stage — stage left, center, downstage. Rotation is which direction they face — toward the audience, turned sideways, looking up. Scale is like a spotlight zoom — making them appear larger or smaller to the audience.
Just like in dance, order matters. If you tell a dancer "take two steps forward, then turn right," they end up in a different place than "turn right, then take two steps forward." Three.js applies transforms in a fixed order: Scale, then Rotate, then Translate.
Scale
Resize the object
Rotate
Spin it to face a direction
Translate
Move it to a position
Hands-On: Moving Things Around
Let's place, rotate, and scale objects step by step. Every 3D interaction you'll ever build starts with these fundamentals.
Step 1: Position — where in space
// Position is [x, y, z]
// x = left/right, y = up/down, z = forward/back
<mesh position={[2, 0, 0]}> {/* 2 units right */}
<boxGeometry />
<meshStandardMaterial color="coral" />
</mesh>
<mesh position={[0, 1.5, 0]}> {/* 1.5 units up */}
<sphereGeometry args={[0.5]} />
<meshStandardMaterial color="skyblue" />
</mesh>
<mesh position={[-1, 0, 3]}> {/* left and forward */}
<coneGeometry args={[0.5, 1]} />
<meshStandardMaterial color="gold" />
</mesh>Position takes an [x, y, z] array. In Three.js, Y points up (not Z like some other tools). Positive X is right, positive Y is up, positive Z comes toward the camera.
Step 2: Rotation — which way it faces
// Rotation is in RADIANS, not degrees!
// Math.PI = 180 degrees
// Math.PI / 2 = 90 degrees
// Math.PI / 4 = 45 degrees
<mesh rotation={[Math.PI / 4, 0, 0]}>
{/* Tilted 45 degrees on X axis */}
<boxGeometry />
<meshStandardMaterial color="coral" />
</mesh>
// Rotate on multiple axes
<mesh rotation={[0, Math.PI / 3, Math.PI / 6]}>
<boxGeometry />
<meshStandardMaterial color="skyblue" />
</mesh>Three.js uses Euler angles in radians. Each value rotates around its respective axis: X tilts forward/back, Y spins left/right, Z rolls clockwise/counter-clockwise. The default rotation order is XYZ.
Step 3: Scale — how big it is
// Uniform scale — same on all axes
<mesh scale={1.5}>
<boxGeometry />
<meshStandardMaterial color="coral" />
</mesh>
// Non-uniform scale — different per axis
<mesh scale={[2, 0.5, 1]}>
{/* Wide and flat */}
<boxGeometry />
<meshStandardMaterial color="skyblue" />
</mesh>
// Scale 0 on an axis = flat (invisible from side)
<mesh scale={[1, 0, 1]}>
{/* Completely flat! */}
</mesh>Scale can be a single number (uniform) or an [x, y, z] array (non-uniform). A scale of 1 is the original size. Scale 2 doubles it. Scale 0.5 halves it. Be careful with non-uniform scale — it can distort normals and break lighting.
What you just learned
Position [x, y, z] places objects in world space. Y is up in Three.js.
Rotation uses radians, not degrees. Math.PI = 180 degrees, Math.PI/2 = 90 degrees.
Scale can be uniform (single number) or per-axis [x, y, z]. Non-uniform scale can distort normals.
Three.js applies transforms in order: Scale first, then Rotate, then Translate. Use parent groups for different ordering.
Question
If you want a mesh to orbit around a point (like a planet orbiting the sun), you need to translate then rotate. But Three.js always rotates before translating. How would you solve this using the scene graph?
Think about it...
You have a mesh at position [3, 0, 0] with rotation [0, Math.PI/2, 0]. Where does it end up?
Hint: Remember: Three.js positions are world-space coordinates, and rotations happen locally around the object's center...
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set all scale to 2 — double size!
Try This!
Beginner
Rotate Y to PI — face backward
Try This!
Beginner
Toggle autoRotate — constant spin
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// WRONG: 90 here means 90 RADIANS!
// That's about 14.3 full rotations!
<mesh rotation={[90, 0, 0]}>
<boxGeometry />
</mesh>// Non-uniform scale distorts normals!
<mesh scale={[1, 5, 1]}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial />
</mesh>
// Lighting looks wrong on the stretched parts// In Three.js, transforms apply as:
// Scale -> Rotate -> Translate
// This is built-in and you can't change it
// But if you WANT a different order,
// just setting position and rotation
// won't give orbital movement:
mesh.position.set(2, 0, 0);
mesh.rotation.y = angle;
// This rotates in place, doesn't orbit!Best Practices
Always Use Radians
Three.js uses radians everywhere. Create a mental map: Math.PI/6 = 30deg, Math.PI/4 = 45deg, Math.PI/2 = 90deg, Math.PI = 180deg.
Prefer Uniform Scale
Use the same value for X, Y, Z scale when possible. Non-uniform scale distorts normals and can cause lighting artifacts on PBR materials.
Use Groups for Complex Transforms
Need orbital motion or pivot points? Nest meshes inside groups. The group's transform applies to all children, giving you layered transform control.
Animate with useFrame, Not State
For smooth animation, modify transforms directly in useFrame using refs. Avoid setting React state 60 times per second — it causes unnecessary re-renders.