基于矩形框选的Three.js DXF画布缩放功能实现问题
框选缩放实现方案(基于Three.js正交相机)
针对你的问题,核心是解决屏幕像素坐标转Three.js世界坐标、正交相机的缩放与位置调整,以及修复代码中的this绑定问题,以下是具体实现步骤:
1. 修复this绑定问题
你的completeZoomIn/completeZoomOut方法在调用时会丢失this指向,导致无法访问this.camera,需要在传递回调时绑定this:
zoomIn() { console.log('zoom in'); // 绑定this,确保completeZoomIn中能访问到Raycasting实例的属性 this._drawZoomRectangle(this.completeZoomIn.bind(this)); } zoomOut() { console.log('zoom out'); this._drawZoomRectangle(this.completeZoomOut.bind(this)); }
2. 优化鼠标坐标获取(相对canvas)
原setMousePosition方法存在兼容性问题,改为直接获取鼠标相对于canvas的坐标,更准确:
_drawZoomRectangle(callback) { const container = document.getElementById('canvas3d'); const rect = container.getBoundingClientRect(); // 获取canvas的视口信息 function setMousePosition(e) { // 直接计算鼠标相对于canvas的坐标,无需兼容旧浏览器 mouse.x = e.clientX - rect.left; mouse.y = e.clientY - rect.top; } var mouse = { x: 0, y: 0, startX: 0, startY: 0 }; var element = null; // 后续mousemove/mousedown/mouseup逻辑保持不变,仅修改setMousePosition部分 container.onmousemove = function (e) { setMousePosition(e); if (element !== null) { element.style.width = Math.abs(mouse.x - mouse.startX) + 'px'; element.style.height = Math.abs(mouse.y - mouse.startY) + 'px'; element.style.left = (mouse.x - mouse.startX < 0) ? mouse.x + 'px' : mouse.startX + 'px'; element.style.top = (mouse.y - mouse.startY < 0) ? mouse.y + 'px' : mouse.startY + 'px'; } }; container.onmousedown = function () { mouse.startX = mouse.x; mouse.startY = mouse.y; element = document.createElement('div'); element.className = 'zoom-rectangle'; element.style.left = mouse.x + 'px'; element.style.top = mouse.y + 'px'; container.appendChild(element); container.style.cursor = 'crosshair'; }; container.onmouseup = function () { element.remove(); element = null; container.style.cursor = 'default'; callback(mouse); }; }
3. 实现框选区域的缩放逻辑
在completeZoomIn方法中,将屏幕框选坐标转换为世界空间坐标,计算新的相机zoom和位置:
import { Raycaster, Vector2, Vector3 } from 'three'; // 新增Vector2/Vector3导入 // ... 类中其余方法 ... completeZoomIn(mouse) { const container = this.container; const camera = this.camera; // 1. 获取框选的两个对角点(相对canvas的坐标) const startX = mouse.startX; const startY = mouse.startY; const endX = mouse.x; const endY = mouse.y; // 2. 转换为Three.js标准化设备坐标(NDC),范围[-1, 1] // 注意:Three.js的Y轴向上,屏幕Y轴向下,需要反转 const toNDC = (coord, size, isY = false) => { const normalized = (coord / size) * 2 - 1; return isY ? -normalized : normalized; }; const startNDC = new Vector2( toNDC(startX, container.clientWidth), toNDC(startY, container.clientHeight, true) ); const endNDC = new Vector2( toNDC(endX, container.clientWidth), toNDC(endY, container.clientHeight, true) ); // 3. 将NDC转换为世界空间坐标(正交相机下,z值取0即可) const startWorld = startNDC.clone().unproject(camera); const endWorld = endNDC.clone().unproject(camera); // 4. 计算框选区域的中心、宽度和高度 const centerWorld = new Vector3( (startWorld.x + endWorld.x) / 2, (startWorld.y + endWorld.y) / 2, camera.position.z // 保持相机Z轴位置不变 ); const boxWidth = Math.abs(endWorld.x - startWorld.x); const boxHeight = Math.abs(endWorld.y - startWorld.y); // 过滤过小的框选,避免异常缩放 if (boxWidth < 1 || boxHeight < 1) return; // 5. 计算当前相机的视口尺寸(世界空间) const currentViewportWidth = (camera.right - camera.left) / camera.zoom; const currentViewportHeight = (camera.top - camera.bottom) / camera.zoom; // 6. 计算新的zoom值:取宽高方向的最小比例,避免画面拉伸 const zoomRatioX = currentViewportWidth / boxWidth; const zoomRatioY = currentViewportHeight / boxHeight; const newZoom = camera.zoom * Math.min(zoomRatioX, zoomRatioY); // 7. 更新相机参数 camera.position.copy(centerWorld); // 将相机移动到框选区域中心 camera.zoom = newZoom; camera.updateProjectionMatrix(); // 必须更新投影矩阵,使参数生效 // 8. 更新射线检测阈值 this._calculateThreshold(); }
4. 补充说明
- 上述逻辑基于正交相机(符合three-dxf-viewer的相机类型),你的代码中
_calculateThreshold使用了camera.left/right/top/bottom,确定是正交相机类型。 completeZoomOut可根据需求实现,比如直接将camera.zoom减半,或反向计算缩放比例。- 记得给
zoom-rectangle类添加CSS样式,让框选矩形可见:
.zoom-rectangle { position: absolute; border: 1px solid #0096ff; background-color: rgba(0, 150, 255, 0.2); pointer-events: none; /* 避免遮挡鼠标事件 */ }
内容的提问来源于stack exchange,提问作者nn_k
相关产品推荐
相关产品推荐

