You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于矩形框选的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:37:35