Navigate

Search topics across all sections

GitHub
Transforms & Color

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.

terminal
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

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

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

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

1
Using degrees instead of radians for rotation
rotation={[90, 0, 0]} rotates WAY more than 90 degrees
Don't do this
App.tsxTSX
// WRONG: 90 here means 90 RADIANS!
// That's about 14.3 full rotations!
<mesh rotation={[90, 0, 0]}>
  <boxGeometry />
</mesh>
Three.js uses radians, not degrees. 90 radians is about 5,157 degrees — you'll get unexpected results. Use Math.PI (180 degrees) or THREE.MathUtils.degToRad() for conversions.
2
Scaling non-uniformly without understanding side effects
Normals get distorted, lighting looks wrong
Don't do this
App.tsxTSX
// Non-uniform scale distorts normals!
<mesh scale={[1, 5, 1]}>
  <sphereGeometry args={[1, 32, 32]} />
  <meshStandardMaterial />
</mesh>
// Lighting looks wrong on the stretched parts
Non-uniform scaling (different X/Y/Z values) distorts surface normals, making lighting appear incorrect. When possible, adjust geometry dimensions directly rather than using non-uniform scale. If you must, recompute normals afterward.
3
Forgetting that transform order matters
Rotating then translating gives a different result than translating then rotating
Don't do this
App.tsxTSX
// 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!
Three.js applies transforms in a fixed order: Scale, then Rotate, then Translate. If you need a different order (like orbiting), use parent groups. The child's position becomes the orbit radius, and the parent's rotation drives the 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.