Skip to content

117. 实战:酷家乐装修编辑器(二十一)

Published:

上节把家具在 2D 视图画了出来。

并且 2D 视图编辑会同步 3D 视图:

2025-07-02 14.33.27.gif

3D 视图编辑会同步 2D 视图:

2025-07-02 17.29.44.gif

但我们需要分别切换 2D、3D 视图,不能同时看。

而在酷家乐里,3D 视图会在右上角展示 2D 视图:

2025-07-02 20.13.18.gif

2D 视图会在右上角展示 3D 视图:

image.png

我们也来实现这种交互。

其实也很简单,之前是切换显示隐藏,修改的是 display 样式:

image.png

现在改为修改宽高。

我们除了要改 div 的宽高外,还要改 threejs 渲染的 canvas 的宽高:

image.png

所以我们要在 threejs 初始化的时候暴露一个改变宽高的方法:

image.png

function changeSize(isBig: boolean) {
    if(isBig) {
        const width = window.innerWidth;
        const height = window.innerHeight - 60;

        renderer.setSize(width,height);

        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    } else {
        const width = 240;
        const height = 200;

        renderer.setSize(width,height);

        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    }
}

就是如果 isBig 是 true 就把画布大小设置为窗口宽高,否则设置 240 * 200

把之前设置 display 的逻辑去掉:

image.png

image.png

然后调用下 changeSize 方法:

image.png

const changeSize3DRef = useRef<(isBig: boolean) => void>(null);
changeSize3DRef.current = changeSize;

调用下:

image.png

useEffect(() => {
    const changeSize3D = changeSize3DRef.current!;

    changeSize3D(false);

}, []);

改了宽高还得改下样式:

image.png

#threejs-3d-container {
    position: absolute;
    z-index: 2;
    right: 0;
}
#threejs-2d-container {
    position: absolute;
    z-index: 1;
    right: 0;
}

把 3D 的 z-index 改大点,然后 right 为 0,在右上角。

image.png

没啥问题。

canvas 宽高设置好了,div 没有设置宽高,会由内容撑开,所以也是一样的宽高:

image.png

image.png

所以我们只需要在点击按钮的时候,修改两个 canvas 的宽高就好了。

在 2D 的初始化代码里也加上这个方法:

image.png

function changeSize(isBig: boolean) {
    if(isBig) {
        const width = window.innerWidth;
        const height = window.innerHeight - 60;

        renderer.setSize(width,height);

        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    } else {
        const width = 240;
        const height = 200;

        renderer.setSize(width,height);

        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    }
}

也同样接收一下:

image.png

image.png

const changeSize2DRef = useRef<(isBig: boolean) => void>(null);
changeSize2DRef.current = changeSize;

之前有一个状态记录了当前视图:

image.png

把它提到上面来。

然后把设置 size 的移到最后,因为要等那两个场景初始化完才能拿到这俩方法:

image.png

curMode 变化的时候,修改两个视图的 size,并且还要改下 index。

useEffect(() => {
    const changeSize3D = changeSize3DRef.current!;
    const changeSize2D = changeSize2DRef.current!;

    if(curMode === '2d') {
        changeSize3D(false);
        changeSize2D(true);
    } else {
        changeSize3D(true);
        changeSize2D(false);
    }
}, [curMode]);
<div id="threejs-3d-container" style={{ zIndex: curMode ==='2d' ? 2 : 1}}></div>
 <div id="threejs-2d-container" style={{ zIndex: curMode ==='3d' ? 2 : 1}}></div>

试下效果:

2025-07-02 21.07.40.gif

切换 2D 视图的时候,3D 视图在右上角。

切换 3D 视图的时候,2D 视图在右上角。

但有个问题:

2025-07-02 21.09.51.gif

当视图变小之后,家具就没法编辑了。

还有一个是 resize 的时候,小视图突然变大了:

2025-07-02 21.12.56.gif

这俩问题都是因为我们之前都是用的大的 width、height:

image.png

我们在窗口 resize 的时候,拿到 canvas 大小,如果是 200,那就不变了:

image.png

const size = renderer.getSize(new THREE.Vector2());

if(size.y === 200) {
    return;
}

另一个视图也是这么改:

image.png

再试下:

2025-07-02 21.18.07.gif

现在窗口 resize 的时候,就只有大的视图会重新设置 canvas 大小了。

再来解决小视图没法编辑家具位置的问题:

image.png

image.png

const { x: width, y: height} = renderer.getSize(new THREE.Vector2());

同样的方式,width、height 改成动态取的 canvas 宽高就可以了。

试一下:

2025-07-02 21.21.49.gif

2025-07-02 21.22.21.gif

现在就可以操作小视图了。

案例代码上传了小册仓库

总结

这节我们实现了大小视图。

之前切换 2D、3D 视图的时候是修改 display 来实现显示隐藏。

现在是切换 canvas 的 width、height 和 z-index。

我们在 threejs 初始化的时候暴露了一个修改 size 的方法给组件里调用,切换视图的时候调用来设置 canvas 的 size。

此外,要注意窗口 resize 的时候,处理鼠标事件的时候都要改为动态获取 width、height 才可以。

评论