Damping and Limits

Damping, Limits and Auto-Rotation

enableDamping = true adds Inertia's inertia (dampingFactor 0.05) and then needs controls.update(delta) every frame. Distance, polar and azimuth limits, enablePan and enableZoom keep the view flattering, and autoRotate turns a showcase. Without a DOM element the controls even run in Node:

Auto-rotation stopped by an azimuth limit, run in NodeJavaScript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const camera = new THREE.PerspectiveCamera();
camera.position.set(0, 1, 4);
const controls = new OrbitControls(camera);             // no element: driven by code alone
Object.assign(controls, {
  autoRotate: true, autoRotateSpeed: 2,                 // 2 means one orbit per 30 s
  minAzimuthAngle: -Math.PI / 4, maxAzimuthAngle: Math.PI / 4,   // stay in front of the shelf
  maxPolarAngle: Math.PI / 2, minDistance: 2, maxDistance: 6,    // above the floor, near
});
for (let second = 1; second <= 5; second++) {
  for (let frame = 0; frame < 60; frame++) controls.update(1 / 60);
  const azimuth = THREE.MathUtils.radToDeg(controls.getAzimuthalAngle());
  console.log(`${second} s: azimuth ${azimuth.toFixed(1)} degrees`);
}
Output
1 s: azimuth -12.0 degrees
2 s: azimuth -24.0 degrees
3 s: azimuth -36.0 degrees
4 s: azimuth -45.0 degrees
5 s: azimuth -45.0 degrees

Given a delta, update() turns at a per-second speed (12 degrees), and the azimuth limit stops it at 45.

OrbitControls with damping, auto-rotation and azimuth, polar and distance limitsHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f5f1e8; font: 13px system-ui, sans-serif; color: #263238; }
  canvas { display: block; max-width: 100%; }
  .ui { padding: 4px 8px; }
</style>
<script type="importmap">
{ "imports": {
  "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div class="ui"><label><input type="checkbox" id="auto" checked> autoRotate</label>
  <label><input type="checkbox" id="damp" checked> enableDamping</label>
  <label><input type="checkbox" id="lim" checked> ±45° azimuth limit</label> <span id="az"></span></div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 280);
document.body.prepend(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color('#fbf8f2');
const camera = new THREE.PerspectiveCamera(35, 600 / 280, 0.1, 50);
camera.position.set(0, 0.6, 3.6);
scene.add(new THREE.HemisphereLight('#ffffff', '#8d6e63', 1.2));
const sun = new THREE.DirectionalLight('#ffffff', 2.5);
sun.position.set(2, 3, 4);
scene.add(sun);
const floor = new THREE.Mesh(new THREE.CircleGeometry(3, 48), new THREE.MeshStandardMaterial({ color: '#e6ddcc' }));
floor.rotation.x = -Math.PI / 2;
floor.position.y = -0.55;
scene.add(floor);
const wall = new THREE.Mesh(new THREE.PlaneGeometry(4, 2.2), new THREE.MeshStandardMaterial({ color: '#d7cbb6' }));
wall.position.set(0, 0.5, -0.3);
scene.add(wall);
const plank = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.06, 0.4), new THREE.MeshStandardMaterial({ color: '#8d6e63' }));
plank.position.y = -0.52;
scene.add(plank);
['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((color, i) => {
  const book = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.98, 0.12), new THREE.MeshStandardMaterial({ color }));
  book.position.x = (i - 2.5) * 0.5;
  scene.add(book);
});

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, -0.05, 0);
Object.assign(controls, {
  enableDamping: true, dampingFactor: 0.05,          // inertia after a drag
  autoRotate: true, autoRotateSpeed: 4,              // 2 would be one orbit per 30 s
  maxPolarAngle: Math.PI / 2 - 0.05,                 // never below the floor
  minDistance: 2, maxDistance: 6,
  enablePan: false,
});
const $ = (id) => document.getElementById(id);
function applyLimits() {
  controls.autoRotate = $('auto').checked;
  controls.enableDamping = $('damp').checked;
  const limit = $('lim').checked ? Math.PI / 4 : Infinity;   // stay in front of the shelf
  controls.minAzimuthAngle = -limit;
  controls.maxAzimuthAngle = limit;
}
['auto', 'damp', 'lim'].forEach((id) => $(id).addEventListener('change', applyLimits));
applyLimits();

let direction = 1;
const timer = new THREE.Timer();
renderer.setAnimationLoop(() => {
  timer.update();
  const azimuth = THREE.MathUtils.radToDeg(controls.getAzimuthalAngle());
  // At a limit auto-rotation stops, so reverse it to show the showcase swinging between the limits
  if ($('lim').checked && Math.abs(azimuth) > 44.5) direction = -Math.sign(azimuth);
  controls.autoRotateSpeed = 4 * direction;
  controls.update(timer.getDelta());                  // needed every frame with damping or autoRotate
  $('az').textContent = `azimuth ${azimuth.toFixed(1)}°`;
  renderer.render(scene, camera);
});
</script>