☰
Vue3+Cesium 鼠标键盘交互大全(拾取、自定义相机、WASD 漫游、事件冲突处理)
2026/11/4 7:11:11 网站建设 项目流程

前言

Cesium 交互分两大块:

  1. 内置相机交互:ScreenSpaceCameraController,控制原生拖拽、旋转、缩放开关
  2. 自定义屏幕事件:ScreenSpaceEventHandler,用来做点击拾取、标绘、hover、右键菜单
  3. 键盘交互:原生没有自带 WASD 漫游,需要监听键盘 +camera移动 API 自己实现第一人称漫游

开发痛点:做标绘绘制的时候,原生鼠标拖拽地图和绘制事件打架;很多人不知道怎么单独禁用旋转、平移,而不是直接关掉所有交互。

环境:Vue3 + Vite + vite-plugin-cesium,和前面两篇保持一致

一、核心 API 速查表

1. ScreenSpaceEventType 鼠标事件枚举(高频)

Cesium.ScreenSpaceEventType.LEFT_CLICK // 左键单击 Cesium.ScreenSpaceEventType.LEFT_DOWN // 左键按下 Cesium.ScreenSpaceEventType.LEFT_UP // 左键抬起 Cesium.ScreenSpaceEventType.RIGHT_CLICK // 右键单击 Cesium.ScreenSpaceEventType.MIDDLE_CLICK // 中键单击 Cesium.ScreenSpaceEventType.MOUSE_MOVE // 鼠标移动 Cesium.ScreenSpaceEventType.WHEEL // 滚轮 Cesium.ScreenSpaceEventType.LEFT_DRAG // 左键拖拽

2. ScreenSpaceCameraController 相机控制器开关(重点!解决标绘冲突)

const controller = viewer.scene.screenSpaceCameraController controller.enableInputs = true; // 总开关,全部禁用=false controller.enableTranslate = true; // 平移(左键拖动地图) controller.enableZoom = true; // 滚轮缩放 controller.enableRotate = true; // 旋转(右键拖拽) controller.enableTilt = true; // 倾斜俯仰(中键拖拽) controller.enableLook = true; // 环视(Shift+左键)

✅标绘绘制时方案:绘制开始关闭平移旋转,绘制结束恢复

3. Camera 相机移动方法(WASD 漫游核心)

viewer.camera.moveForward(meters) // 向前(视线方向) viewer.camera.moveBackward(meters) // 向后 viewer.camera.moveLeft(meters) // 向左 viewer.camera.moveRight(meters) // 向右 viewer.camera.moveUp(meters) // 向上 viewer.camera.moveDown(meters) // 向下

二、完整 Vue3 代码:拾取 + WASD 漫游 + 事件开关

<template> <div class="cesium-wrap"> <div class="btn-group"> <button @click="toggleCameraControl">开关原生相机</button> <button @click="startWASD">开启WASD漫游</button> <button @click="stopWASD">关闭WASD漫游</button> <button @click="addTestPoint">添加测试点位(可点击拾取)</button> </div> <div class="cesium-container" ref="cesiumRef"></div> </div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue' import * as Cesium from 'cesium' import 'cesium/Build/Cesium/Widgets/widgets.css' const cesiumRef = ref(null) let viewer = null let handler = null let cameraController = null // WASD状态 const keyState = {} let isWASDRunning = false let preRenderCallback = null // 初始化 function initViewer() { viewer = new Cesium.Viewer(cesiumRef.value, { animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, fullscreenButton: false, navigationHelpButton: false }) cameraController = viewer.scene.screenSpaceCameraController viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(108.95, 34.27, 80000) }) handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) bindMouseEvent() } // ========== 鼠标事件:实体拾取、hover ========== function bindMouseEvent() { // 左键拾取实体 handler.setInputAction((e) => { const pickResult = viewer.scene.pick(e.position) if (Cesium.defined(pickResult) && Cesium.defined(pickResult.id)) { const entity = pickResult.id console.log('✅ 拾取到实体:', entity.name) alert(`拾取:${entity.name}`) } else { console.log('未拾取到实体') } }, Cesium.ScreenSpaceEventType.LEFT_CLICK) // 鼠标移动hover handler.setInputAction((e) => { const pickResult = viewer.scene.pick(e.endPosition) if (Cesium.defined(pickResult) && Cesium.defined(pickResult.id)) { document.body.style.cursor = 'pointer' } else { document.body.style.cursor = 'default' } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) } // ========== 开关原生相机所有交互 ========== function toggleCameraControl() { cameraController.enableInputs = !cameraController.enableInputs alert(cameraController.enableInputs ? '✅ 已开启地图拖拽缩放' : '❌ 已关闭地图拖拽缩放') } // ========== WASD 第一人称漫游 ========== function startWASD() { if (isWASDRunning) return isWASDRunning = true // 关闭原生相机,避免冲突 cameraController.enableInputs = false // canvas获取焦点,才能捕获键盘 viewer.canvas.setAttribute('tabindex', '0') viewer.canvas.focus() // 键盘按下 window.addEventListener('keydown', onKeyDown) // 键盘抬起 window.addEventListener('keyup', onKeyUp) // 每帧更新相机位置 preRenderCallback = viewer.scene.preRender.addEventListener(() => { const camera = viewer.camera // 根据相机高度自适应速度,越高移动越快 const height = Cesium.Cartographic.fromCartesian(camera.position).height const speed = height / 120 if (keyState['w']) camera.moveForward(speed) if (keyState['s']) camera.moveBackward(speed) if (keyState['a']) camera.moveLeft(speed) if (keyState['d']) camera.moveRight(speed) if (keyState['q']) camera.moveUp(speed) if (keyState['e']) camera.moveDown(speed) }) } function stopWASD() { isWASDRunning = false window.removeEventListener('keydown', onKeyDown) window.removeEventListener('keyup', onKeyUp) if (preRenderCallback) { viewer.scene.preRender.removeEventListener(preRenderCallback) preRenderCallback = null } // 恢复原生相机 cameraController.enableInputs = true } function onKeyDown(e) { keyState[e.key.toLowerCase()] = true } function onKeyUp(e) { keyState[e.key.toLowerCase()] = false } // 添加测试点 function addTestPoint() { viewer.entities.add({ name: '可点击监测点', position: Cesium.Cartesian3.fromDegrees(108.95, 34.27), point: { pixelSize: 14, color: Cesium.Color.RED } }) } onMounted(() => { initViewer() }) onUnmounted(() => { stopWASD() handler?.destroy() viewer?.destroy() }) </script> <style scoped> .cesium-wrap { width: 100%; height: 100vh; } .btn-group { padding: 10px; background: #fff; z-index: 9; position: relative; } button { margin: 0 4px; padding: 6px 10px; } .cesium-container { width: 100%; height: calc(100vh - 50px); } </style>

三、拾取两种方式区分(高频踩坑)

// 方式1:scene.pick() → 拾取Entity、Billboard(2D标注,不拾取地形和3DTiles深度) const pick = viewer.scene.pick(position) // 方式2:scene.pickPosition() → 拾取带深度坐标,地形、3DTiles都能拿到笛卡尔坐标 const cartesian = viewer.scene.pickPosition(position) // 方式3:globe.pick() → 只拾取地表地形,就是标绘那篇用的 const ray = viewer.camera.getPickRay(position) const cartesian = viewer.scene.globe.pick(ray, viewer.scene)

业务建议:

  • 点击 Entity 弹窗:scene.pick()
  • 鼠标在地面标绘:globe.pick()
  • 拾取倾斜模型、BIM 模型:pickPosition()

四、标绘场景事件冲突最佳实践(重点)

就是承接上一篇绘制多边形代码,绘制开启的时候只关闭平移旋转,绘制结束恢复:

// 开始绘制时 function startDraw() { cameraController.enableTranslate = false cameraController.enableRotate = false cameraController.enableTilt = false } // 绘制结束/取消绘制 function finishDraw() { cameraController.enableTranslate = true cameraController.enableRotate = true cameraController.enableTilt = true }

好处:滚轮缩放还可以正常使用,不会一刀切全部关掉交互,用户体验更好。

五、常见坑汇总

  1. WASD 按键没反应
    canvas 默认无法捕获键盘,必须设置tabindex+canvas.focus();
  2. 开启 WASD 后地球不能拖拽
    代码里我们主动关闭了enableInputs,漫游模式就是第一人称,本来就不和原生拖拽共存;
  3. ScreenSpaceEventHandler多次叠加监听
    一定要onUnmounted执行handler.destroy();组件多次销毁重建会多次触发事件,点一次执行多次;
  4. pickPosition 深度不准、拾取不到
    需要开启深度检测:viewer.scene.globe.depthTestAgainstTerrain = true;开启后性能会下降;
  5. 右键菜单冲突
    Cesium 右键默认旋转,想要自定义右键菜单:关闭enableRotate+ 原生e.preventDefault()阻止浏览器菜单;
  6. preRender 每帧回调不要写耗时逻辑
    WASD 里面只做相机移动,不要请求、大量循环,否则掉帧。

六、拓展方向

  1. 鼠标右键菜单(点击点位弹出菜单)
  2. 鼠标框选矩形,批量拾取 Entity
  3. 漫游时增加视角俯仰(鼠标右键按住旋转视角),做成类似游戏第一人称
  4. 键盘快捷键:ESC 取消绘制、Delete 删除选中实体
  5. 封装通用useCesiumInteractionhook,把拾取、WASD、相机开关抽离复用

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询