首页 / Three.js 入门教程 / OrbitControls 轨道控制器

Three.js 入门教程

OrbitControls 轨道控制器

本教程共 40 篇 · 第 21 篇 · 更新于 2026-08-14 · 约 8 分钟阅读

Three.jsOrbitControls相机控制交互阻尼事件

本节目标:学会引入 OrbitControls,让相机能绕着场景旋转、缩放、平移,并掌握 target、距离角度限制、阻尼和事件。学完你能给任何场景加上一套顺手的观察视角。

为什么需要控制器

前面章节里,相机位置写死在代码中。想看物体的另一面,只能改代码、刷新页面。真实项目里,用户需要自己转动视角。

three.js 核心只负责渲染,不提供交互。控制相机这类功能,放在官方扩展(addons)里。最常用的就是 OrbitControls(轨道控制器)。

它像一台围着目标转的相机:镜头始终指向一个中心点,拖拽时相机绕着中心旋转。产品展示、模型查看、场景浏览,都靠它。

引入 OrbitControls

OrbitControls 不在核心包里,要从 addons 路径引入:

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

importmap 已经把 three/addons/ 前缀映射到 CDN 的 examples/jsm 目录,这条 import 直接可用。回顾第 3 章的 importmap 配置:

  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
    }
  }
  </script>

构造控制器需要两个参数:相机,以及监听的 DOM 元素。一般传 renderer.domElement,这样只有画布上的操作会触发控制,页面其他部分不受影响:

const controls = new OrbitControls(camera, renderer.domElement);

第一个可运行示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>OrbitControls 入门</title>
  <style>
    * { margin: 0; padding: 0; }
    html, body { height: 100%; }
    canvas { display: block; }
  </style>
  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
    }
  }
  </script>
</head>
<body>
  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

    // 场景
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1a1a2e);

    // 相机
    const camera = new THREE.PerspectiveCamera(
      60, window.innerWidth / window.innerHeight, 0.1, 100
    );
    camera.position.set(4, 3, 6);

    // 立方体 + 网格地面
    const cube = new THREE.Mesh(
      new THREE.BoxGeometry(1.5, 1.5, 1.5),
      new THREE.MeshStandardMaterial({ color: 0x00d4ff })
    );
    scene.add(cube);

    const grid = new THREE.GridHelper(10, 10);
    scene.add(grid);

    // 灯光:标准材质需要光才看得见
    const light = new THREE.DirectionalLight(0xffffff, 3);
    light.position.set(5, 10, 5);
    scene.add(light);

    // 渲染器
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    document.body.appendChild(renderer.domElement);

    // 轨道控制器
    const controls = new OrbitControls(camera, renderer.domElement);

    // 渲染循环
    function animate() {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>

保存后用本地服务器打开。左键拖拽旋转,滚轮缩放,右键拖拽平移,立刻就有操作 3D 模型的感觉。

三种操作方式

OrbitControls 默认支持三种操作,桌面端和触屏端都已内置:

操作鼠标触屏
旋转左键拖拽单指拖动
缩放滚轮双指捏合
平移右键拖拽双指拖动

每种操作可以单独关掉,属性名很好记,默认都是 true:

controls.enableZoom = false;   // 禁止缩放
controls.enableRotate = false; // 禁止旋转
controls.enablePan = false;    // 禁止平移

整个控制器也能整体禁用:

controls.enabled = false;

移动端不用额外配置,触摸手势默认开启。想要方向键平移,调用一次 listenToKeyEvents:

controls.listenToKeyEvents(window);
Tip

展示型页面(比如产品 3D 预览)常关掉平移,只留旋转和缩放,防止用户把视角拖丢。

它是怎么工作的

理解原理,配置就不靠背了。相机始终在一个”看不见的球面”上运动,球心就是 target。相机在球面上的位置由三个量决定:

  • 距离:相机到球心的远近
  • 极角:相机偏离正上方的角度
  • 方位角:相机绕竖直轴的水平角度

OrbitControls 内部就是用这三个量描述相机位置的。后面所有限制属性,都是对这三个量设上下限:

属性限制的量
minDistance / maxDistance距离
minPolarAngle / maxPolarAngle极角
minAzimuthAngle / maxAzimuthAngle方位角

看懂这张表,限制类属性的用途就全清楚了。

target:绕哪个点转

目标点是球心,默认在原点 (0, 0, 0)。它存在 controls.target 里,是一个 Vector3。

物体不在原点时,把 target 指向物体,转起来才舒服:

controls.target.copy(cube.position);

注意平移时 target 会跟着移动。想要固定视角中心,把 enablePan 关掉。

Note

手动移动相机后,记得让相机继续”看向”目标。比如飞到物体面前:camera.position.set(0, 2, 5); controls.target.set(0, 1, 0);,然后调 controls.update()。只挪相机不挪 target,相机会自动转头看向目标,结果常和你预想的不一样。

限制距离与角度

缩放不是无限的。用 minDistance / maxDistance 限制相机到目标点的距离:

controls.minDistance = 2;  // 最近 2 个单位
controls.maxDistance = 20; // 最远 20 个单位

超出范围后滚轮会”卡住”,不会穿进物体,也不会退到无限远。注意 minDistance 应大于相机的 near 值,maxDistance 应小于 far 值。

垂直方向用极角限制,从正上方 0 到正下方 π:

controls.minPolarAngle = 0;            // 默认,可转到正上方
controls.maxPolarAngle = Math.PI;      // 默认,可转到正下方

把 maxPolarAngle 设为 Math.PI / 2,相机就不会转到地平线以下。模拟”地面”场景的常用设置,官方 orbit 示例就这么做。水平方向用 minAzimuthAngle / maxAzimuthAngle,默认 ±Infinity,即不限制。

Note

three.js 里角度一律用弧度。90° 是 Math.PI / 2,180° 是 Math.PI。别直接写 90,那会被当成 90 弧度。

平移与缩放细节

两个容易忽略的属性,实战中常遇到。

screenSpacePanning 决定平移沿什么平面走。默认 true(沿屏幕平面平移,跟鼠标拖拽方向一致);设成 false 则沿”地面”(相机 up 正交平面)平移,符合看地形的直觉:

controls.screenSpacePanning = false; // 沿"地面"平移,符合看地形的直觉

zoomToCursor 决定缩放是否朝鼠标位置缩放。默认 false:画面中心不动,滚轮缩放。设成 true 后,鼠标指哪,画面就往哪放大,查看局部细节很舒服:

controls.zoomToCursor = true;

阻尼:让运动更顺滑

默认的操作手感是”立刻停止”,有点生硬。开启阻尼后,松手时相机带一点惯性滑行,像有重量一样:

controls.enableDamping = true;
controls.dampingFactor = 0.05; // 越小惯性越长

关键点:开了阻尼,必须每帧调用 controls.update(),否则相机纹丝不动:

function animate() {
  requestAnimationFrame(animate);
  controls.update(); // 阻尼和自动旋转都依赖它
  renderer.render(scene, camera);
}
animate();
Note

没开阻尼时,update() 不调用也能用。但每帧调用的习惯最省心,任何配置下都安全。这是新手最容易踩的坑:开了 enableDamping 忘了 update(),然后怀疑控制器坏了。

自动旋转

展台效果:相机自己绕着目标慢慢转,适合没人操作时的展示:

controls.autoRotate = true;
controls.autoRotateSpeed = 2; // 旋转速度,默认 2

autoRotate 同样依赖每帧 update()。用户开始拖拽时自动旋转暂停,松手后恢复。

事件:change / start / end

OrbitControls 派发三个事件:

  • change:相机被控制器改变时触发
  • start:用户开始交互(按下鼠标、手指落下)时触发
  • end:用户结束交互时触发

监听方式和 DOM 事件一样:

controls.addEventListener('start', () => console.log('开始操作'));
controls.addEventListener('change', () => console.log('相机动了'));
controls.addEventListener('end', () => console.log('操作结束'));

change 最实用的场景是”按需渲染”:静态页面不开渲染循环,只在相机变化时画一帧,省电省性能:

controls.addEventListener('change', () => renderer.render(scene, camera));

两种渲染模式对比:

模式做法适用
连续渲染动画循环每帧渲染有动画的场景
按需渲染change 事件里渲染静态展示页
Tip

已经在跑动画循环的页面不需要 change 监听,每帧都会渲染。按需渲染适合展示型静态页面,移动端更省电。

其他常用能力

几个高频方法,快速过一遍:

controls.saveState();           // 记住当前视角
controls.reset();               // 恢复到保存的视角;没保存过就回到初始位置

controls.getPolarAngle();       // 当前极角
controls.getAzimuthalAngle();   // 当前方位角
controls.getDistance();         // 相机到目标点的距离

最后两个方法常用来做 UI 联动,比如显示”俯视 / 侧视 / 平视”的状态。页面销毁或切换场景时,调用 dispose() 移除控制器注册的所有事件监听,防止泄漏:

controls.dispose();

小结

  • 引入路径:three/addons/controls/OrbitControls.js
  • 三种操作:旋转 / 缩放 / 平移,可分别禁用
  • 原理:相机在球面上运动,target 是球心
  • minDistance / maxDistance 限制距离,minPolarAngle / maxPolarAngle 限制俯仰
  • enableDamping 与 autoRotate 必须每帧 update()
  • 事件:change / start / end

下一章看其他控制器:拖拽物体的 TransformControls、第一人称 PointerLockControls 等。