Three.js 快速入门教程【十五】交互神器DragControls使用详解,实现对物体或模型拖拽

请添加图片描述

系列文章目录

Three.js 快速入门教程【一】开启你的 3D Web 开发之旅
Three.js 快速入门教程【二】透视投影相机
Three.js 快速入门教程【三】渲染器
Three.js 快速入门教程【四】三维坐标系
Three.js 快速入门教程【五】动画渲染循环
Three.js 快速入门教程【六】相机控件 OrbitControls
Three.js 快速入门教程【七】常见几何体类型
Three.js 快速入门教程【八】常见材质类型
Three.js 快速入门教程【九】光源类型
Three.js 快速入门教程【十】常见的纹理类型
Three.js 快速入门教程【十一】天空盒的多种实现方式
Three.js 快速入门教程【十二】外部模型加载
Three.js 快速入门教程【十三】外部模型加载后常见的处理操作
Three.js 快速入门教程【十四】使用Stats.js监控渲染帧率和性能优化
Three.js 快速入门教程【十五】交互神器DragControls使用详解,实现对物体或模型拖拽
Three.js 快速入门教程【十六】调试神器 gui.js使用详解,可视化面板控制场景参数提高开发效率
Three.js 快速入门教程【十七】射线拾取模型——射线与射线投射器Raycaster介绍
Three.js 快速入门教程【十八】射线拾取模型——鼠标点击屏幕选中模型或物体
Three.js 快速入门教程【十九】CSS2D渲染器介绍,实现场景中物体或设备标注标签信息
Three.js 快速入门教程【二十】三维模型优化实战:使用gltf-pipeline与Draco对模型进行压缩,提高加载速度和流畅性
Three.js 快速入门教程【二十一】CSS3D渲染器(CSS3DRenderer、CSS3DObject 、CSS3DSprite)介绍,实现场景中物体标注标签信息
Three.js 快速入门教程【二十二】动画神器Tween.js使用指南



一、前言

      在 Three.js 中创建 3D 场景时,让用户能够与场景中的物体进行交互是提升用户体验的重要部分。DragControls 是 Three.js 提供的一个非常实用的工具,它允许用户通过鼠标拖动场景中的物体,从而实现更直观的交互。本文将详细介绍如何在 Three.js 项目中使用 DragControls。


二、DragControls简介

DragControls 是 Three.js 官方提供的交互辅助类,用于实现物体在三维空间中的拖拽操作。它的核心原理是:

  • 基于 CSS 坐标系的屏幕点击检测
  • 通过 Raycaster 进行三维物体拾取
  • 自动处理拖拽过程中的坐标转换

使用场景:
1、普通场景中对某个物体进行拖拽,实现平移
2、加载外部3D 模型对整体拖拽或零部件拖拽拆解
3、在 3D 地图应用中,用户可能需要拖动地图上的特定元素,如标记点、建筑物等,以便进行位置调整或交互操
4、在一些简单的 3D 游戏中,玩家可能需要拖动游戏中的道具、角色或障碍物


三、DragControls使用讲解

3.1 引入库

import { DragControls } from "three/examples/jsm/controls/DragControls.js";

3.2 创建实例

构造函数:

 new DragControls( objects, camera, renderer.domElement );
  • objects:要进行拖拽操作的 THREE.Object3D 对象数组,这些对象可以是网格、几何体等。
  • camera:场景中使用的相机对象,用于确定拖拽操作的视角。
  • renderer.domElement:渲染器的 DOM 元素,用于监听鼠标事件。

3.3 常见属性

属性名类型说明默认值
enabledBoolean用于启用或禁用拖拽功能true
transformGroupBoolean当拖动组中的一个对象时,组中的所有对象是否一起移动false
distanceMinNumber表示允许的最小拖拽距离0
distanceMaxNumber表示允许的最大拖拽距离Infinity
panSpeedNumber控制平移操作的速度0.3

示例:

//创建实例
const dragControls = new DragControls( objects, camera, renderer.domElement );

dragControls.enabled = false; // 禁用拖拽功能
controls.transformGroup = true; // 拖动组中一个对象时,组内所有对象一起移动
dragControls.distanceMin = 0.1; // 设置最小拖拽距离为 0.1
dragControls.distanceMax = 100; // 设置最大拖拽距离为 100
dragControls.panSpeed = 0.5; // 设置平移速度为 0.5

3.4 方法

方法名入参说明
update——该方法需要在每一帧渲染循环中调用,用于更新拖拽操作的状态
addobject向 DragControls 中添加一个新的可拖拽对象
removeobject从 DragControls 中移除一个可拖拽对象
resetobject从 DragControls 中移除一个可拖拽对象
remove——重置拖拽操作的状态,例如将物体的位置、旋转和缩放恢复到初始状态
dispose——释放控件占用的资源,移除所有事件监听器。通常在不再需要使用控件时调用
addEventListener( type:String, listener:Function )为控件添加事件监听器。支持的事件类型有 dragstart(开始拖动)、drag(拖动过程中)和 dragend(拖动结束)
removeEventListener( type:String, listener:Function )移除指定类型的事件监听器

示例:

1、渲染循环中调用,用于更新拖拽操作的状态

function animate() {
    requestAnimationFrame( animate );
    dragControls.update(); // 更新拖拽状态
    renderer.render( scene, camera );
}
animate();

2、添加一个新的可拖拽对象。

const newObject = new THREE.Mesh( geometry, material );
dragControls.add( newObject ); // 添加新的可拖拽对象

3、添加事件监听器

dragControls.addEventListener( 'dragstart', function( event ) {
    console.log('拖拽开始');
} );

dragControls.addEventListener( 'drag', function( event ) {
    console.log('正在拖拽');
} );
dragControls.addEventListener( 'dragend', function( event ) {
    console.log('拖拽结束');
} );

4、拖拽轴向约束

// 自定义拖拽逻辑
dragControls.addEventListener('drag', (event) => {
  // 限制X轴移动
  event.object.position.x = Math.min(5, Math.max(-5, event.object.position.x));
  // 锁定Y轴位置
  event.object.position.y = 0;
});

5、结束释放资源

const controls = new DragControls( objects, camera, renderer.domElement );
// 不再使用控件时
controls.dispose();

四、完整代码示例

4.1普通物体拖拽

import * as THREE from "three";
//引入相机控制器
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
//引入拖拽控制器
import { DragControls } from "three/examples/jsm/controls/DragControls.js";

// 创建场景
const scene = new THREE.Scene();
//坐标辅助线
const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

// 创建相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  3000
);
camera.position.set(0, 5, 10);

// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);

//添加平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 2, 5);
scene.add(directionalLight);

// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
//创建材质
const material = new THREE.MeshLambertMaterial({ color: 0x8844aa });
// 创建网格对象
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建 OrbitControls 控件
const orbitControls = new OrbitControls(camera, renderer.domElement);

// 初始化DragControls
const dragControls = new DragControls([mesh], camera, renderer.domElement);

dragControls.addEventListener("dragstart", (event) => {
  // 拖拽开始时触发
  orbitControls.enabled = false; // 禁用其他控制器(如 OrbitControls)
});

dragControls.addEventListener("dragend", (event) => {
  // 拖拽结束时触发
  orbitControls.enabled = true; // 启用其他控制器
});

dragControls.addEventListener("drag", (event) => {
  // 拖拽过程中持续触发
  console.log("拖拽中:", event.object.position);
});
// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  orbitControls.update();
  dragControls.update(); // 更新拖拽状态
  renderer.render(scene, camera);
}

animate();

运行效果:
请添加图片描述

需要注意的是如果和其他控制器同时使用(如 OrbitControls),避免冲突,需要在开始拖拽回调中禁用其他控制器,在结束拖拽回调中恢复其他控制器,如上述示例处理。


4.2 外部模型整体或零部件拖拽

外部模型可以看成由很多子网格模型组合而成,通过transformGroup属性可以控制是否整体拖拽,值为true时外部模型整体拖拽,false时子网格模型(零部件)单独拖拽。

import * as THREE from "three";
//引入相机控制器
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
//引入拖拽控制器
import { DragControls } from "three/examples/jsm/controls/DragControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

// 创建场景
const scene = new THREE.Scene();
//坐标辅助线
const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

// 创建相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  3000
);
camera.position.set(0, 5, 30);

// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);

//添加平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 5);
directionalLight.position.set(0, 2, 5);
scene.add(directionalLight);

// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建 OrbitControls 控件
const orbitControls = new OrbitControls(camera, renderer.domElement);

//DragControls控件
let dragControls = null;

// 加载GLTF模型
let loader = new GLTFLoader();
loader.load(
  "test.gltf",
  (object) => {
    const model = object.scene;
    model.position.set(0, 0, 0);
    camera.lookAt(model.position);
    scene.add(model);
    // 模型拖拽处理
    handleDrag([model]);
  },
  undefined,
  (error) => {
    console.error(error);
  }
);

//模型拖拽处理
function handleDrag(draggableObjects) {
  dragControls = new DragControls(
    draggableObjects, // 可拖拽的物体数组
    camera, // 相机对象
    renderer.domElement // 渲染器的 DOM 元素
  );

  dragControls.transformGroup = false; // 设置为 true,将多个对象作为一个整体拖动
  dragControls.addEventListener("dragstart", (event) => {
    // 拖拽开始时触发
    orbitControls.enabled = false; // 禁用其他控制器(如 OrbitControls)
  });

  dragControls.addEventListener("dragend", (event) => {
    // 拖拽结束时触发
    orbitControls.enabled = true; // 启用其他控制器
  });

  dragControls.addEventListener("drag", (event) => {
    // 拖拽过程中持续触发
    console.log("拖拽中:", event.object.position);
  });
}

// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  orbitControls.update();
  dragControls && dragControls.update(); // 更新拖拽状态
  renderer.render(scene, camera);
}

animate();

运行效果:

请添加图片描述

把dragControls.transformGroup 设置为true,观察拖拽情况

dragControls.transformGroup=true

请添加图片描述


五、总结

       通过本教程,您将掌握 Three.js 中实现 3D 物体拖拽的核心技术,能够在虚拟现实、3D 编辑器、加载外部模型等场景中灵活运用。建议结合实际项目需求,进一步扩展交互逻辑和视觉反馈效果。

更多three.js入门知识点请关注该系列教程后续的更新。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pixle0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值