首页 / Three.js 入门教程 / 其他控制器

Three.js 入门教程

其他控制器

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

Three.jsTransformControlsPointerLockControlsFlyControlsTrackballControlsArcballControls

本节目标:认识 OrbitControls 之外的五种官方控制器,知道每种控制器的适用场景和基本用法。学完你能按需求挑选合适的控制器,并组合出”拖拽 + 观察”的编辑体验。

控制器全家桶

OrbitControls 适合观察,但很多应用需要别的交互。官方 addons 的 controls 目录下,还有这些控制器:

控制器用途交互方式
TransformControls在场景里拖拽物体鼠标拖拽操作杆
PointerLockControls第一人称视角(FPS)鼠标锁定 + 键盘移动
FlyControls飞行漫游,无重力约束键盘 + 鼠标
TrackballControls轨迹球,可任意翻转鼠标三键
ArcballControls无万向锁的轨道旋转鼠标 + 触屏

它们都在 three/addons/controls/ 目录下,引入方式和 OrbitControls 一样。

更新机制不一样

学控制器之前,先搞懂一个区别:有的控制器需要每帧更新,有的不需要。用错就”控制器没反应”。

控制器每帧 update()说明
OrbitControls阻尼 / 自动旋转时需要其他情况可不调
TransformControls不需要事件驱动,拖拽即生效
PointerLockControls不需要事件驱动,移动自己算
FlyControls需要,传 delta(秒)不调不动
TrackballControls需要不调不动
ArcballControls需要不调不动

TransformControls 和 PointerLockControls 是事件驱动的,操作立刻生效。其余几个每帧都要调 update(),FlyControls 还要把帧间隔时间传进去。记住这张表,后面每个都不踩坑。

TransformControls:拖拽物体

编辑类应用里,需要直接拖动场景中的物体。TransformControls 在物体上显示一个操作杆(gizmo),拖拽即可改变位置、旋转或缩放:

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

const transform = new TransformControls(camera, renderer.domElement);
transform.attach(cube);              // 绑定要操作的物体
scene.add(transform.getHelper());    // 操作杆本体要加进场景

三种模式,对应快捷键 W / E / R:

transform.setMode('translate'); // W 键:移动
transform.setMode('rotate');    // E 键:旋转
transform.setMode('scale');     // R 键:缩放

世界坐标、局部坐标切换,以及吸附对齐也都内置:

transform.setSpace('local');             // 按物体自身坐标轴
transform.setTranslationSnap(1);         // 移动吸附到整数单位
transform.setRotationSnap(Math.PI / 12); // 旋转每 15° 一格
transform.setScaleSnap(0.25);            // 缩放按 0.25 步进

它和 OrbitControls 同时使用时有个经典坑:拖操作杆时,视角也会跟着转。解决方法是监听 dragging-changed 事件,拖拽期间禁用轨道控制器。完整示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>TransformControls + 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';
    import { TransformControls } from 'three/addons/controls/TransformControls.js';

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1a1a2e);
    scene.add(new THREE.GridHelper(10, 10));

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

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

    const light = new THREE.DirectionalLight(0xffffff, 3);
    light.position.set(5, 10, 5);
    scene.add(light);

    // 观察用:轨道控制器
    const orbit = new OrbitControls(camera, renderer.domElement);
    orbit.enableDamping = true;

    // 编辑用:变换控制器
    const cube = new THREE.Mesh(
      new THREE.BoxGeometry(1.5, 1.5, 1.5),
      new THREE.MeshStandardMaterial({ color: 0x00d4ff })
    );
    scene.add(cube);

    const transform = new TransformControls(camera, renderer.domElement);
    transform.attach(cube);
    scene.add(transform.getHelper());

    // 拖操作杆时暂停轨道控制
    transform.addEventListener('dragging-changed', (event) => {
      orbit.enabled = !event.value;
    });

    // W / E / R 切换模式
    window.addEventListener('keydown', (event) => {
      if (event.key === 'w') transform.setMode('translate');
      if (event.key === 'e') transform.setMode('rotate');
      if (event.key === 'r') transform.setMode('scale');
    });

    function animate() {
      requestAnimationFrame(animate);
      orbit.update();
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>
Note

操作杆(getHelper 返回的对象)必须加入场景,否则看不见也拖不动。官方 misc_controls_transform 示例就是”TransformControls + OrbitControls”的组合。

PointerLockControls:第一人称

PointerLockControls 基于浏览器的 Pointer Lock API:点击后鼠标指针被锁定,移动鼠标转动视角,适合 FPS 类游戏:

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

const controls = new PointerLockControls(camera, document.body);
scene.add(controls.object); // 相机作为场景子对象,方便整体移动

// 点击后锁定鼠标
document.addEventListener('click', () => controls.lock());

controls.addEventListener('lock', () => console.log('已锁定'));
controls.addEventListener('unlock', () => console.log('已解锁'));

它只负责转动视角,移动要靠键盘。官方 pointerlock 示例里,用 WASD 记录方向,每帧调用移动方法:

let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;

// keydown / keyup 里按 event.code 设置四个布尔值

const velocity = new THREE.Vector3();
const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);
  const delta = clock.getDelta();

  // 前进后退
  velocity.z = (moveForward ? 1 : 0) - (moveBackward ? 1 : 0);
  controls.moveForward(-velocity.z * 50 * delta);
  // 左右平移
  velocity.x = (moveRight ? 1 : 0) - (moveLeft ? 1 : 0);
  controls.moveRight(velocity.x * 50 * delta);

  renderer.render(scene, camera);
}
animate();

距离由速度乘以帧间隔时间算出,这样不同帧率下移动速度一致。moveForward 的负号是因为它按”前进方向”为正,算出来的速度向量方向相反。

Note

指针锁定后,普通 mousemove 拿不到位移,必须靠控制器处理视角。进入游戏前的遮罩提示、按 Esc 退出后的恢复,都通过 lock / unlock 事件处理。完整套路参考官方 misc_controls_pointerlock 示例。

FlyControls:飞行漫游

FlyControls 模拟飞行器操作:WASD 前后左右移动,R/F 上升下降,Q/E 翻滚,方向键控制俯仰和偏航。没有”地面”概念,可以倒着飞,适合城市漫游、太空场景:

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

const controls = new FlyControls(camera, renderer.domElement);
controls.movementSpeed = 50; // 移动速度
controls.rollSpeed = 0.5;    // 翻滚速度
controls.dragToLook = true;  // 按住鼠标拖动才转视角

注意它的 update() 需要传时间间隔(秒),用 Clock 计算:

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);
  const delta = clock.getDelta();
  controls.update(delta);
  renderer.render(scene, camera);
}
animate();

autoForward 设为 true 时,不按键也会缓慢前进,像开了自动驾驶。

TrackballControls:轨迹球

TrackballControls 和 OrbitControls 很像,但不保持相机的”上”方向。你可以把视角转到天翻地覆,没有约束。左键旋转、中键缩放、右键平移,还支持 A / S / D 键:

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

const controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 1;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;

它每帧都要调用 update()。两个常用配置:

controls.staticMoving = false;       // false 时带阻尼
controls.dynamicDampingFactor = 0.2; // 阻尼强度
Tip

大多数场景用 OrbitControls 就够。TrackballControls 的优势是完全自由的视角,代价是用户容易转晕。需要”上下不颠倒”的场合别用它。

ArcballControls:无万向锁的轨道

ArcballControls 是较新的轨道控制器,内部用四元数计算旋转,没有万向锁问题。单指旋转、双指缩放平移,旋转带平滑惯性,手感接近建模软件。构造时多接收一个 scene 参数:

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

const controls = new ArcballControls(camera, renderer.domElement, scene);
controls.enableGrid = false; // 关闭辅助网格
controls.dampingFactor = 8;  // 惯性强度
controls.minDistance = 1;
controls.maxDistance = 20;

几个特色方法:

controls.focus(cube);             // 平滑聚焦到某个物体
controls.copyState();             // 复制当前视角状态
controls.pasteState();            // 粘贴刚才复制的视角
controls.reset();                 // 复位
controls.setGizmosVisible(false); // 隐藏屏幕上的操作提示圆环

开启 enableAnimations(默认开启)时,旋转、缩放都带平滑过渡。enableFocus 开启时,双击物体可以直接聚焦过去。触屏上它的手势比 OrbitControls 更细腻,平板设备上体验好。

还有两个小控制器

DragControls 让物体直接跟着鼠标走,不需要操作杆,适合”拖拽摆放”这类简单需求。构造参数是物体数组:

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

const drag = new DragControls(objects, camera, renderer.domElement);
drag.addEventListener('dragstart', () => (orbit.enabled = false));
drag.addEventListener('dragend', () => (orbit.enabled = true));

事件有 dragstart / drag / dragend,拖拽时同样要暂停 OrbitControls,不然视角和物体一起乱动。

FirstPersonControls 是早期的第一人称方案,鼠标拖动控制视角、WASD 移动。不需要指针锁定,但交互偏旧,新项目建议直接用 PointerLockControls,这里不展开。

键盘操作一览

控制器大多内置键盘绑定,写操作说明时对照这张表:

控制器按键
OrbitControls方向键平移(需 listenToKeyEvents)
TransformControlsW / E / R 切换模式,Q 切换坐标系,X / Y / Z 锁轴,Shift 吸附
PointerLockControls无内置移动键,移动代码自己写
FlyControlsWASD 移动,R / F 升降,Q / E 翻滚,方向键俯仰偏航
TrackballControlsA 旋转,S 缩放,D 平移

表里没有的按键行为,多半要自己监听 keydown 实现,控制器不会替你处理。

怎么选

一句话总结选择思路:

  • 观察展示 → OrbitControls(上一章)
  • 拖拽编辑物体 → TransformControls
  • FPS 游戏 → PointerLockControls
  • 自由飞行漫游 → FlyControls
  • 完全自由的旋转 → TrackballControls 或 ArcballControls

控制器可以组合。TransformControls 配 OrbitControls 是最常见的编辑方案;PointerLockControls 的场景里一般不需要第二个控制器。

Note

所有控制器都在监听 DOM 事件。页面销毁、切换场景时记得清理:OrbitControls、TransformControls、PointerLockControls、FlyControls 都有 dispose() 方法,调用后监听器会被移除。ArcballControls 与 TrackballControls 也都有 dispose(),页面卸载时统一调用。