在处理 Three.js 场景中的鼠标事件时,有时会遇到拖拽操作触发点击事件的问题。为了防止这种情况,可以通过区分拖拽和点击事件来解决这个问题。一般的思路是检测鼠标按下和松开的位置,如果两者之间的距离很小,就认为是点击事件,否则就是拖拽事件。
以下是修改后的代码,通过在 mousedown
和 mouseup
事件之间记录鼠标的位置来区分拖拽和点击事件:
<script>
import modules from "./modules/index.js";
import * as THREE from "three";
import gsap from "gsap";
const path = require('path');
import { GUI } from 'dat.gui';
let viewer = null
let office = null
let oldOffice = {}
let gltf75 = {}
// eslint-disable-next-line no-unused-vars
let cityv1 = null
// eslint-disable-next-line no-unused-vars
let modelSelectName = null
// eslint-disable-next-line no-unused-vars
let modelMoveName = null
// eslint-disable-next-line no-unused-vars
let isModelSelectName = false
let che, cheLable
// const gui = new dat.GUI();
const gui = new GUI();
export default {
name: "Three",
data() {
return {
autoRotate: false,
isDriver: false,
dialogVisible: false,
loading: true,
mouseDownPosition: new THREE.Vector2(), // 记录鼠标按下位置
mouseUpPosition: new THREE.Vector2() // 记录鼠标松开位置
}
},
mounted() {
this.init()
setTimeout(() => {
this.loading = false
}, 10000)
},
destroyed() {
console.log(1)
},
methods: {
autoScaleAndCenterModel(model) {
const box = new THREE.Box3().setFromObject(model); // 计算模型的边界框
// const center = box.getCenter(new THREE.Vector3()); // 获取模型的中心点
const center = new THREE.Vector3(0, 0, 0);
const size = box.getSize(new THREE.Vector3()); // 获取模型的尺寸
// 计算模型的最大尺寸,并使用它来确定相机的距离
const maxDim = Math.max(size.x, size.y, size.z);
const fov = viewer.camera.fov * (Math.PI / 180); // 将视角转换为弧度
const aspect = viewer.renderer.domElement.clientWidth / viewer.renderer.domElement.clientHeight;
// 计算相机的Z位置
let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2)) * 0.6; // 增加一些缓冲,以确保模型完全显示
viewer.camera.position.set(center.x, center.y, cameraZ); // 设置相机位置
// viewer.camera.lookAt(center); // 相机看向模型中心
viewer.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相机看向原点
// 更新控件目标
viewer.controls.target.copy(center);
viewer.controls.update(); // 更新控件
// 更新相机的纵横比
viewer.camera.aspect = aspect;
viewer.camera.updateProjectionMatrix(); // 更新相机投影矩阵
},
driverView() {
this.isDriver = !this.isDriver
},
//切换广告牌视角
billboardView() {
this.isDriver = false
gsap.to(viewer.camera.position, {
x: -2326,
y: 853,
z: 3156,
duration: 2,
ease: "power1.inOut",
onComplete: () => {
},
});
gsap.to(viewer.controls.target, {
x: -685,
y: 0,
z: 545,
duration: 2,
ease: "power1.inOut",
onComplete: () => {
},
});
},
autoRotateClick() {
viewer.controls.autoRotate = !viewer.controls.autoRotate
this.autoRotate = viewer.controls.autoRotate
},
resetScene() {
gsap.to(viewer.camera.position, {
x: 17,
y: 10,
z: 52,
duration: 2,
ease: "Bounce.inOut",
});
gsap.to(viewer.controls.target, {
x: 0,
y: 0,
z: 0,
duration: 2,
ease: "power1.inOut",
onComplete: () => {
},
});
gsap.to(viewer.scene.children.find(o => o.name == '人').rotation, {
y: 0,
duration: 2,
ease: "power1.inOut",
});
this.isDriver = false
cheLable.visible = true
viewer.scene.children[viewer.scene.children.findIndex(o => o.name == 'XCN物流')].visible = true
viewer.scene.children[viewer.scene.children.findIndex(o => o.name == '树')].visible = true
viewer.scene.children[viewer.scene.children.findIndex(o => o.name == 'cityv1')].visible = true
viewer.scene.children[viewer.scene.children.findIndex(o => o.name == '实验楼')] = gltf75.clone()
viewer.scene.children[viewer.scene.children.findIndex(o => o.name == '办公大厅')] = office.object = oldOffice.clone()
modelSelectName = null
modelMoveName = null
isModelSelectName = false
},
init() {
viewer = new modules.Viewer('viewer-container') //初始化场景
let skyBoxs = new modules.SkyBoxs(viewer)//添加天空盒和雾化效果
// let EffectComposer = new modules.EffectComposer(viewer)//添加天空盒和雾化效果
skyBoxs.addSkybox(2)
viewer.camera.position.set(17, 10, 52) //设置相机位置
//限制controls的上下角度范围
viewer.controls.maxPolarAngle = Math.PI / 2.1;
let lights = new modules.Lights(viewer)
let ambientLight = lights.addAmbientLight()
ambientLight.setOption({color: 0xffffff, intensity: 1})
lights.addDirectionalLight([100, 100, -10], {
color: 'rgb(253,253,253)',
intensity: 3,
castShadow: true,
})
let modeloader = new modules.ModelLoder(viewer)
let tiemen = {}
// const modelPath = '3d/la_night_city.glb';
// const modelPath = '3d/shanghaiqing.glb';
const modelPath = '3d/caidi.glb';
// const modelPath = path.join(__static, '3d/la_night_city.glb');
console.log('modelPath', modelPath)
console.log('modelPath', __dirname)
// modeloader.loadModelToScene('3d/la_night_city.glb', _model => {
modeloader.loadModelToScene(modelPath, _model => {
_model.object.name = 'cityv1'
_model.openCastShadow()
_model.openReceiveShadow()
// 自动缩放和居中模型
this.autoScaleAndCenterModel(_model.object)
this.billboardView()
tiemen = tiemen = {
fun: moveOnCurve,
content: che
}
viewer.addAnimate(tiemen)
// cityv1 = _model.object.clone()
}, (progress) => {
progress = Math.floor(progress * 100)
if (progress === 100) {
this.loading = false
}
}, (error) => {
this.loading = false
console.log('error', error)
})
// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5); // 参数是坐标轴的长度
viewer.scene.add(axesHelper);
// 添加 dat.GUI 调试界面
const gui = new GUI();
const cameraFolder = gui.addFolder('Camera');
const cameraParams = {
x: viewer.camera.position.x,
y: viewer.camera.position.y,
z: viewer.camera.position.z
};
cameraFolder.add(cameraParams, 'x', -100, 100).name('Position X').onChange(value => {
viewer.camera.position.x = value;
viewer.render();
});
cameraFolder.add(cameraParams, 'y', -100, 100).name('Position Y').onChange(value => {
viewer.camera.position.y = value;
viewer.render();
});
cameraFolder.add(cameraParams, 'z', -100, 100).name('Position Z').onChange(value => {
viewer.camera.position.z = value;
viewer.render();
});
cameraFolder.open();
const lookAt = new THREE.Vector3(0, 0, 0);
const targetParams = {
x: lookAt.x,
y: lookAt.y,
z: lookAt.z
};
const targetFolder = gui.addFolder('LookAt');
targetFolder.add(targetParams, 'x', -100, 100).name('Target X').onChange(value => {
lookAt.x = value;
viewer.camera.lookAt(lookAt);
viewer.render();
});
targetFolder.add(targetParams, 'y
', -100, 100).name('Target Y').onChange(value => {
lookAt.y = value;
viewer.camera.lookAt(lookAt);
viewer.render();
});
targetFolder.add(targetParams, 'z', -100, 100).name('Target Z').onChange(value => {
lookAt.z = value;
viewer.camera.lookAt(lookAt);
viewer.render();
});
targetFolder.open();
viewer.render();
let dom = document.querySelector('#viewer-container canvas')
dom.addEventListener('mousedown', this.onMouseDown)
dom.addEventListener('mouseup', this.onMouseUp)
},
// 处理鼠标按下事件
onMouseDown(event) {
this.mouseDownPosition.set(event.clientX, event.clientY);
},
// 处理鼠标松开事件
onMouseUp(event) {
this.mouseUpPosition.set(event.clientX, event.clientY);
const deltaX = this.mouseUpPosition.x - this.mouseDownPosition.x;
const deltaY = this.mouseUpPosition.y - this.mouseDownPosition.y;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 5) {
// 距离很小,认为是点击事件
this.onClick(event);
} else {
// 距离较大,认为是拖拽事件
console.log('Drag event detected');
}
},
// 处理点击事件
onClick(event) {
console.log('Click event detected');
// 添加你需要的点击事件处理逻辑
},
}
}
</script>
说明:
- 记录鼠标按下位置:在
mousedown
事件中,记录鼠标按下的位置。 - 记录鼠标松开位置:在
mouseup
事件中,记录鼠标松开的位置,并计算按下和松开位置之间的距离。 - 判断事件类型:如果按下和松开位置之间的距离小于某个阈值(如5像素),认为是点击事件,否则认为是拖拽事件。
通过这种方式,可以有效区分拖拽和点击操作,从而防止拖拽过程中误触发点击事件。