在处理 Three.js 场景中的鼠标事件时,有时会遇到拖拽操作触发点击事件的问题。为了防止这种情况,可以通过区分拖拽和点击事件来解决这个问题。一般的思路是检测鼠标按下和松开的位置,如果两者之间的距离很小,就认为是点击事件,否则就是拖拽事件。

以下是修改后的代码,通过在 mousedownmouseup 事件之间记录鼠标的位置来区分拖拽和点击事件:

<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>

说明:

  1. 记录鼠标按下位置:在 mousedown 事件中,记录鼠标按下的位置。
  2. 记录鼠标松开位置:在 mouseup 事件中,记录鼠标松开的位置,并计算按下和松开位置之间的距离。
  3. 判断事件类型:如果按下和松开位置之间的距离小于某个阈值(如5像素),认为是点击事件,否则认为是拖拽事件。

通过这种方式,可以有效区分拖拽和点击操作,从而防止拖拽过程中误触发点击事件。

threejs,拖拽的时候会触发点击事件_拖拽