Three.js 使用 TransformControls 实现模型拖拽交互
作者:小算子时间:2026-11-01 02:14:15浏览:639
本文介绍如何在 Three.js 中使用 TransformControls 控制器实现 3D 模型的拖拽交互。通过初始化场景、引入控制器并监听事件,结合模型与连线更新逻辑,实现类似 Blender 的变换操作。文章提供完整代码示例及效果演示,帮助开发者快速掌握模型交互功能。
一、原理:
TransformControls 是 Three.js 内置的控制器组件之一。
该组件旨在提供类似专业数字内容创作工具(如 Blender)中的模型交互体验,允许用户在三维空间中对物体进行变换操作。与场景相机控制器不同,TransformControls 专注于物体本身的变换,不会干扰场景摄像机的视角控制。
更多详细信息可参考官方文档。
二、步骤:
- 1. 初始化场景
- 2. 引入 TransformControls 控制器,并绑定相关事件监听
- 3. 添加模型及辅助连线
- 4. 根据控制器的变换状态动态更新连线位置
三、代码:
<template>
<div class="fa_container">
<div class="container" ref="container"></div>
</div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
const scene = new THREE.Scene()
let renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
logarithmicDepthBuffer: true
})
let camera, controls, transformControl
const pointer = new THREE.Vector2()
const point = new THREE.Vector3()
const raycaster = new THREE.Raycaster()
let splineHelperObjects = []
const ARC_SEGMENTS = 200
let splines = []
export default {
name: 'model',
data () {
return {
positionList: [
[10, 0, 0],
[-10, 0, 0]
]
}
},
mounted () {
this.init()
this.addMarkModel()
this.render()
},
methods: {
init () {
this.dom = this.$refs['container']
this.initHelper()
this.initLight()
this.initCamera()
this.initRender()
this.initControls()
this.dom.addEventListener('pointermove', e => {
this.onPointerMove(e)
})
},
initHelper () {
scene.background = new THREE.Color(0xf0f0f0)
// const axes = new THREE.AxesHelper(50)
// scene.add(axes)
const planeGeometry = new THREE.PlaneGeometry(2000, 2000)
planeGeometry.rotateX(-Math.PI / 2)
const planeMaterial = new THREE.ShadowMaterial({ color: 0x000000, opacity: 0.2 })
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.position.y = -200
plane.receiveShadow = true
scene.add(plane)
const helper = new THREE.GridHelper(2000, 100)
helper.position.y = -199
helper.material.opacity = 0.25
helper.material.transparent = true
scene.add(helper)
},
initCamera () {
camera = new THREE.PerspectiveCamera(
45,
this.dom.offsetWidth / this.dom.offsetHeight,
0.001,
10000
)
camera.position.set(35, 35, 35)
},
initLight () {
const directLight = new THREE.DirectionalLight('#ffffff', 0.5)
const directLight1 = new THREE.DirectionalLight('#ffffff', 0.5)
const directLight2 = new THREE.PointLight('#ffffff', 0.5)
const ambientLight = new THREE.AmbientLight('#ffffff', 0.3)
directLight.castShadow = true
directLight.position.set(15, 15, 15)
directLight1.position.set(-15, -15, 15)
ambientLight.position.set(0, 0, -5)
directLight2.position.set(-15, 15, -15)
directLight2.castShadow = true
scene.add(directLight, directLight1, ambientLight, directLight2)
},
initRender () {
renderer.setSize(1902, 935)
renderer.outputEncoding = THREE.sRGBEncoding
this.dom.appendChild(renderer.domElement)
},
initControls () {
controls = new OrbitControls(camera, renderer.domElement)
transformControl = new TransformControls(camera, renderer.domElement)
transformControl.addEventListener('change', () => {
console.log('模型拖动')
})
transformControl.addEventListener('dragging-changed', function (event) {
controls.enabled = !event.value
})
transformControl.addEventListener('objectChange', param => {
this.updateSplineOutline()
})
scene.add(transformControl)
},
// 鼠标移动
onPointerMove (event) {
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(splineHelperObjects, false)
if (intersects.length > 0) {
const object = intersects[0].object
if (object !== transformControl.object) {
transformControl.attach(object)
controls.enabled = false
}
}
},
// 更新连线
updateSplineOutline () {
for (let k = 0; k < splines.length; k++) {
const spline = splines[k]
const splineMesh = spline.mesh
const position = splineMesh.geometry.attributes.position
for (let i = 0; i < ARC_SEGMENTS; i++) {
const t = i / (ARC_SEGMENTS - 1)
spline.getPoint(t, point)
position.setXYZ(i, point.x, point.y, point.z)
}
position.needsUpdate = true
}
},
render () {
requestAnimationFrame(this.render.bind(this))
controls.update()
renderer.render(scene, camera)
},
addMarkModel () {
for (let i = 0; i < this.positionList.length; i++) {
let position = []
let obj1 = this.creatSpot(this.positionList[i], i)
let obj2 = this.creatHtml(i)
setTimeout(() => {
position.push(obj1.position)
position.push(obj2.position)
this.creatLine(position)
}, 2000)
}
},
creatSpot (positionArr, index) {
const spherGeometry = new THREE.SphereGeometry(3, 32, 16)
const spherMaterial = new THREE.MeshLambertMaterial({ color: 'red' })
const sphere = new THREE.Mesh(spherGeometry, spherMaterial)
sphere.position.set(positionArr[0], positionArr[1], positionArr[2])
sphere.scale.set(0.2, 0.2, 0.2)
splineHelperObjects.push(sphere)
scene.add(sphere)
return sphere
},
creatHtml (index, spot) {
const spherGeometry = new THREE.BoxBufferGeometry(15, 15, 15)
const spherMaterial = new THREE.MeshPhongMaterial({
transparent: true,
opacity: 1,
color: 'green'
})
const sphere = new THREE.Mesh(spherGeometry, spherMaterial)
sphere.position.x = Math.random() * 10
sphere.position.y = Math.random() * 20
sphere.position.z = Math.random() * 15
sphere.scale.set(0.2, 0.2, 0.2)
splineHelperObjects.push(sphere)
scene.add(sphere)
return sphere
},
creatLine (position) {
var curve = new THREE.CatmullRomCurve3(position)
var points = curve.getPoints(ARC_SEGMENTS)
var geometry = new THREE.BufferGeometry().setFromPoints(points)
var material = new THREE.LineBasicMaterial({ color: 0xff0000 })
curve.mesh = new THREE.LineSegments(geometry, material)
scene.add(curve.mesh)
splines.push(curve)
}
}
}
</script>
<style>
.container {
width: 1902px;
height: 935px;
overflow: hidden;
background: black;
display: inline-block;
}
</style>
四、最终效果:
拖拽前状态:

鼠标拾取模型:

模型拖动过程:

总结
本文介绍了基于 Three.js 使用 TransformControls 实现模型拖拽的方法。如需了解更多 Three.js 模型交互相关内容,建议查阅本站过往文章或继续浏览后续教程。感谢支持!
作者最新文章
JS开发接入 DeepSeek 实现 AI 智能问诊
2026-10-31 18:58:41
uniapp微信小程序集成xr-frame加载3D模型完整指南
2026-10-31 14:27:13
Wiki.js 支持哪些编辑器?Markdown 与可视化编辑切换指南
2026-10-30 19:14:38
Java与JavaScript的核心差异:运行环境与执行机制对比
2026-10-30 19:01:40
为什么JavaScript基本数据类型是不可变的?
2026-10-30 18:46:50
