Three.js Camera Transitions: Continuous Position, Target, and Projection Changes
Starting with a 3D stackup editor that animates into isometric view but jumps on reset, examine camera state, projection transitions, and reversals during motion.
In a 3D stackup editor, switching from the default perspective view to an isometric view already animated, but clicking reset jumped back abruptly. It is tempting to adjust easing or extend the duration first. The key issue was elsewhere: the two actions did not follow the same state-transition path.
By checking preset entry, return to default, and mid-transition reversals together, I gradually clarified which state a camera animation needs to preserve. This article revisits that approach with simplified explanatory code. It includes no new live 3D demo, and historical browser checks are not presented as results rerun for this article.
Camera position is only part of the image
A camera’s position determines where you look from, but the image also depends on the look-at target, orientation, projection type, zoom, and viewport aspect ratio. Perspective and orthographic cameras at similar positions do not necessarily produce the same apparent scale.
Three.js exposes the projection matrix and its inverse as camera state; see the Camera documentation. In a stackup model, animating only position while switching projection immediately on the first frame still produces an abrupt change in scale and perspective.
OrbitControls’ target also matters. The user may have panned to a detail near the board edge, while a preset assumes the origin as its target. Changing the camera while retaining the old target can make the next drag orbit the wrong center. The OrbitControls documentation describes its target and update behavior.
Route every action through the same transition function
I first list the entry points: perspective presets, isometric presets, reset, and another click during a transition. Each should ultimately pass its target state to a shared entry point instead of setting the camera separately.
That shared entry point first reads the current frame and copies position, target, orientation, and projection state. Copy the values rather than retaining references to mutable Vector3 or Matrix4 objects; otherwise later camera updates also change the supposed starting point.
// Simplified: position and target interpolation for the same camera type only;
// projection switching is omitted.
const fromPosition = camera.position.clone();
const fromTarget = controls.target.clone();
const toPosition = destination.clone();
const toTarget = destinationTarget.clone();
function updateTransition(progress: number) {
const t = Math.max(0, Math.min(1, progress));
const eased = t * t * (3 - 2 * t);
camera.position.lerpVectors(fromPosition, toPosition, eased);
controls.target.lerpVectors(fromTarget, toTarget, eased);
camera.lookAt(controls.target);
// Coordinate this with controls damping, constraints, and frame update order.
}
This code depends on objects such as camera and controls in a Three.js scene; it is not a standalone application. Projection handling is deliberately omitted so a short example does not appear to solve every aspect of switching between perspective and orthographic views.
A reversal should likewise capture a new starting point from the image at that moment. If reset is clicked at 40% progress, it must not restart from the previous preset’s standard coordinates, which would make the first frame jump backward. The new animation may have a new destination, but its starting point must remain continuous.
Projection transitions are a specific tradeoff in this scene
In this project’s implementation, switching between perspective and isometric views handles projection scale before transitioning the projection matrix. At completion, it restores the matrix generated by the target camera’s standard parameters. The inverse matrix must update during the transition too, so ray calculations that depend on it remain consistent with the display.
Two issues are easy to miss. First, projection matrices permit scale equivalence in homogeneous coordinates. Element-wise interpolation of arbitrarily scaled matrices may produce unexpected visual changes, so scale must be handled for the cameras and target distance in use. Second, element-wise interpolation does not guarantee stable, invertible, interactive intermediate states between arbitrary projections.
I therefore treat this as an implementation tradeoff for this stackup view rather than a general camera-animation formula. Clipping planes, depth precision, picking, viewport resizing, and extreme parameters all need checking. Other projects could retain one projection and adjust angle and scale, or design a more explicit switching transition, rather than directly copying matrix interpolation.
Let user interaction take over
Automatic animation and OrbitControls will fight if both write the position. When a user begins dragging or zooming, cancel the automatic transition according to the product’s interaction rules and leave the current state ready for further interaction.
With damping enabled, check residual motion too. Clearing the transition object may not clear changes already accumulated in controls; forcibly resetting controls can cause another jump. The actual frame loop must establish which part owns camera-state updates at each moment.
A viewport resize can also rewrite projection. If the animation sets its starting projection on the first frame and a layout effect immediately updates it for the new viewport, the image can still flash. Initialization, resize, and frame-update ordering must be considered together with the projection transition.
With reduced motion enabled, apply the full target state immediately, keeping position, target, projection, and controls consistent. Skipping animation does not mean skipping state synchronization.
Inspect intermediate frames, not just endpoints
Correct endpoints only show that the final parameters are approximately right; they do not prove a continuous transition. I check entering isometric view, returning to perspective, reset, mid-animation reversal, interruption by dragging, resize, and reduced motion, observing the first, intermediate, and final frames in slow playback or a recording.
Tests can assert state copying, both directional entry points, and matrix updates. Real browsers reveal changes in model scale, board-layer clipping, drag takeover, and visual jumps. A historical fix included browser inspection of intermediate frames in both directions, but this article does not rerun that project. New parameters or control changes still need regression checks.
Problems like this have made me see camera animation as a handoff in state ownership. Unifying entry points and capturing the current frame before handling projection and interaction usually gets closer to the issue than adjusting animation duration alone.