Interactive camera controls enabling users to orbit, zoom, and pan around 3D models with customizable bounds and behaviors.
The primary control for enabling interactive camera manipulation.
/**
* Enable interactive camera controls (orbit, zoom, pan)
* Users can click/drag to orbit, scroll to zoom, and right-click drag to pan
*/
cameraControls: boolean;Usage:
<model-viewer src="models/car.glb" camera-controls></model-viewer>Control the camera's position using spherical coordinates (theta, phi, radius).
/**
* Camera position in spherical coordinates
* Format: "theta phi radius" where:
* - theta: horizontal angle (degrees or radians)
* - phi: vertical angle from positive Y axis (degrees or radians)
* - radius: distance from target (absolute or percentage)
*/
cameraOrbit: string;
/**
* Get current camera position as spherical coordinates
*/
getCameraOrbit(): SphericalPosition;
interface SphericalPosition {
theta: number; // horizontal angle in radians
phi: number; // vertical angle in radians
radius: number; // distance from target
toString(): string;
}Usage:
<!-- Start with front view, slightly elevated, at 105% of auto distance -->
<model-viewer
src="models/robot.glb"
camera-orbit="0deg 75deg 105%"
camera-controls>
</model-viewer>// Get current camera position
const position = modelViewer.getCameraOrbit();
console.log(`Camera at θ:${position.theta}, φ:${position.phi}, r:${position.radius}`);
// Set new position
modelViewer.cameraOrbit = "45deg 60deg 150%";Set what point the camera looks at in 3D space.
/**
* Camera target point in world coordinates
* Format: "x y z" in model units, or "auto" for automatic
*/
cameraTarget: string;
/**
* Get current camera target position
*/
getCameraTarget(): Vector3D;
interface Vector3D {
x: number;
y: number;
z: number;
toString(): string;
}Usage:
<!-- Look at specific point 2 units up from model center -->
<model-viewer
src="models/building.glb"
camera-target="0m 2m 0m"
camera-controls>
</model-viewer>// Get current target
const target = modelViewer.getCameraTarget();
console.log(`Looking at: ${target.x}, ${target.y}, ${target.z}`);
// Focus on a specific part of the model
modelViewer.cameraTarget = "1m 0.5m 0m";Control camera zoom and perspective.
/**
* Camera field of view angle
* Smaller values = more zoomed in, larger values = wider view
* Format: degrees (e.g., "30deg") or "auto" for automatic
*/
fieldOfView: string;
/**
* Get current field of view in degrees
*/
getFieldOfView(): number;
/**
* Get minimum field of view in degrees
*/
getMinimumFieldOfView(): number;
/**
* Get maximum field of view in degrees
*/
getMaximumFieldOfView(): number;
/**
* Get ideal aspect ratio for the model
*/
getIdealAspect(): number;
/**
* Update camera framing for the model
*/
updateFraming(): Promise<void>;
/**
* Zoom by a specified number of key presses (positive = zoom in, negative = zoom out)
*/
zoom(keyPresses: number): void;
/**
* Programmatically interact with the model using finger-like input
*/
interact(duration: number, finger0: Finger, finger1?: Finger): void;Usage:
<!-- Zoomed in view for detail inspection -->
<model-viewer
src="models/watch.glb"
field-of-view="20deg"
camera-controls>
</model-viewer>Limit camera movement to specific ranges.
/**
* Minimum camera orbit bounds
* Format: "minTheta minPhi minRadius"
*/
minCameraOrbit: string;
/**
* Maximum camera orbit bounds
* Format: "maxTheta maxPhi maxRadius"
*/
maxCameraOrbit: string;
/**
* Minimum field of view (maximum zoom in)
*/
minFieldOfView: string;
/**
* Maximum field of view (maximum zoom out)
*/
maxFieldOfView: string;
/**
* Camera movement bounds constraint
* Format: "box" or "sphere" to constrain target movement
*/
bounds: string;Usage:
<!-- Constrain to front hemisphere only -->
<model-viewer
src="models/face.glb"
min-camera-orbit="-90deg 0deg auto"
max-camera-orbit="90deg 90deg auto"
camera-controls>
</model-viewer>
<!-- Limit zoom range -->
<model-viewer
src="models/product.glb"
min-field-of-view="15deg"
max-field-of-view="45deg"
camera-controls>
</model-viewer>Customize interaction prompts and sensitivity.
/**
* Interaction prompt display mode
* - 'auto': Show after delay when no interaction detected
* - 'when-focused': Show only when element is focused
* - 'none': Never show interaction prompt
*/
interactionPrompt: 'auto' | 'when-focused' | 'none';
/**
* Time in milliseconds before showing interaction prompt
*/
interactionPromptThreshold: number;
/**
* Control sensitivity multiplier (higher = more sensitive)
*/
orbitSensitivity: number;
/**
* Zoom sensitivity multiplier (higher = more sensitive)
*/
zoomSensitivity: number;
/**
* Pan sensitivity multiplier (higher = more sensitive)
*/
panSensitivity: number;
/**
* Input sensitivity multiplier (higher = more sensitive)
*/
inputSensitivity: number;
/**
* Reset interaction prompt to initial state
*/
resetInteractionPrompt(): void;Usage:
<!-- Show prompt after 5 seconds, with reduced sensitivity -->
<model-viewer
src="models/delicate.glb"
interaction-prompt="auto"
interaction-prompt-threshold="5000"
orbit-sensitivity="0.5"
camera-controls>
</model-viewer>Disable specific types of camera interactions.
/**
* Disable zoom via scroll wheel or pinch gestures
*/
disableZoom: boolean;
/**
* Disable camera panning (right-click drag or two-finger drag)
*/
disablePan: boolean;
/**
* Disable tap-to-recenter functionality
*/
disableTap: boolean;
/**
* Accessibility translations for screen readers
* Can be an object with translations or string preset name or null
*/
a11y: A11yTranslationsInterface | string | null;Usage:
<!-- Allow orbit only, no zoom or pan -->
<model-viewer
src="models/fixed-view.glb"
camera-controls
disable-zoom
disable-pan>
</model-viewer>Control touch behavior and camera animation.
/**
* CSS touch-action property for touch event handling
* - 'pan-y': Allow vertical page scrolling while model rotates
* - 'none': Block all default touch behaviors
*/
touchAction: string;
/**
* Camera animation interpolation decay rate
* Higher values = snappier movement, lower = smoother
*/
interpolationDecay: number;
/**
* Jump camera to current target position immediately (no animation)
*/
jumpCameraToGoal(): void;Usage:
<!-- Allow page scrolling while interacting -->
<model-viewer
src="models/inline.glb"
camera-controls
touch-action="pan-y">
</model-viewer>// Instant camera movement
modelViewer.cameraOrbit = "90deg 45deg 150%";
modelViewer.jumpCameraToGoal(); // Move immediately without animationFired when camera position, target, or field of view changes.
interface CameraChangeEvent extends Event {
type: 'camera-change';
detail: {
source: 'user-interaction' | 'none' | 'automatic';
};
}Usage:
modelViewer.addEventListener('camera-change', (event) => {
console.log('Camera changed by:', event.detail.source);
// Save current camera position
const orbit = modelViewer.getCameraOrbit();
const target = modelViewer.getCameraTarget();
localStorage.setItem('cameraState', JSON.stringify({
orbit: orbit.toString(),
target: target.toString()
}));
});Create predefined camera positions for guided viewing:
const tourPositions = [
{ orbit: "0deg 75deg 105%", target: "0m 0m 0m" },
{ orbit: "90deg 75deg 105%", target: "0m 1m 0m" },
{ orbit: "180deg 45deg 150%", target: "0m 0.5m 0m" }
];
let currentTour = 0;
function nextTourStop() {
const position = tourPositions[currentTour];
modelViewer.cameraOrbit = position.orbit;
modelViewer.cameraTarget = position.target;
currentTour = (currentTour + 1) % tourPositions.length;
}Adjust camera behavior based on screen size:
function updateCameraForScreenSize() {
if (window.innerWidth < 768) {
// Mobile: more sensitive controls, constrained bounds
modelViewer.orbitSensitivity = 1.5;
modelViewer.maxCameraOrbit = "Infinity 90deg 300%";
modelViewer.touchAction = "pan-y";
} else {
// Desktop: standard settings
modelViewer.orbitSensitivity = 1.0;
modelViewer.maxCameraOrbit = "Infinity 160deg 300%";
modelViewer.touchAction = "none";
}
}
window.addEventListener('resize', updateCameraForScreenSize);