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

如何在model-viewer中同时显示多3D模型并解决缩放问题?

Model-Viewer多模型同时显示与缩放问题

model-viewer效果

问题描述

我设计的项目效果如上,目前遇到两个核心问题:

  • 点击模型按钮(比如船只)时,希望同时显示船只和椅子模型,但当前逻辑是替换model-viewer的src,导致旧模型被新模型覆盖,无法同时展示多个模型。
  • 尝试添加多个模型后,缩放操作出现异常,无法正常控制多模型的视角缩放。

当前使用代码

HTML代码

<!doctype html>
<html lang="en" dir="ltr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
    <title>Model-Viewer</title>
</head>
<body>
<model-viewer src="Chair.glb" poster="Chair.webp" shadow-intensity="1" ar camera-controls touch-action="pan-y" alt="A 3D model carousel">
    <button slot="ar-button" id="ar-button">
        View in your space
    </button>
    <button id="ar-failure">
        AR is not tracking!
    </button>
    <div class="slider">
        <div class="slides">
            <button class="slide selected" onclick="switchSrc(this, 'Chair')" style="background-image: url('Chair.webp');">
            </button>
            <button class="slide" onclick="switchSrc(this, 'Mixer')" style="background-image: url('Mixer.webp');">
            </button><button class="slide" onclick="switchSrc(this, 'GeoPlanter')" style="background-image: url('GeoPlanter.webp');">
            </button><button class="slide" onclick="switchSrc(this, 'ToyTrain')" style="background-image: url('ToyTrain.webp');">
            </button>
            <button class="slide" onclick="switchSrc(this, 'Canoe')" style="background-image: url('Canoe.webp');">
            </button>
        </div>
    </div>
</model-viewer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<script src="script.js"></script>
</body>
</html>

script.js代码

const modelViewer = document.querySelector("model-viewer");
     
window.switchSrc = (element, name) => {
    const base = "/" + name;
    modelViewer.src = base + '.glb';
    modelViewer.poster = base + '.webp';
    const slides = document.querySelectorAll(".slide");
    slides.forEach((element) => {
        element.classList.remove("selected");
    });
    element.classList.add("selected");
}

document.querySelector(".slider").addEventListener('beforexrselect', (ev) => {
    ev.preventDefault();
});

尝试过的无效代码

你之前尝试用A-Frame的<a-entity>标签嵌套在model-viewer中,这种方案无效的原因是model-viewer和A-Frame是独立的3D组件系统,互不兼容,不能直接混用。代码如下:

<model-viewer src="scene.gltf" camera-controls>
  <button onclick="toggleShip()">Toggle Ship</button>
  <button onclick="toggleChair()">Toggle Chair</button>
  <a-entity id="ship" gltf-model="#ship" visible></a-entity>
  <a-entity id="chair" gltf-model="#chair" visible></a-entity>
</model-viewer>

<script>
  const ship = document.querySelector('#ship');
  const chair = document.querySelector('#chair');

  function toggleShip() {
    ship.setAttribute('visible', !ship.getAttribute('visible'));
  }

  function toggleChair() {
    chair.setAttribute('visible', !chair.getAttribute('visible'));
  }
</script>

解决方案

方案一:多model-viewer叠加+相机同步(无需合并模型)

这种方案不需要修改模型文件,通过创建多个model-viewer元素叠加,控制每个模型的显示/隐藏,并同步相机参数解决缩放问题。

修改后的HTML代码

<!doctype html>
<html lang="en" dir="ltr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
    <title>Model-Viewer</title>
    <style>
        /* 容器用来定位多个model-viewer */
        .model-container {
            position: relative;
            width: 100%;
            height: 600px;
        }
        model-viewer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        /* 隐藏模型的类 */
        .hidden-model {
            display: none;
        }
        /* 缩略图按钮样式优化 */
        .slide img {
            width: 100%;
            height: auto;
            object-fit: cover;
        }
    </style>
</head>
<body>
<div class="model-container">
    <!-- 椅子模型viewer,默认显示 -->
    <model-viewer id="chair-viewer" src="Chair.glb" poster="Chair.webp" shadow-intensity="1" ar camera-controls touch-action="pan-y" alt="Chair">
        <button slot="ar-button" id="ar-button">
            View in your space
        </button>
        <button id="ar-failure">
            AR is not tracking!
        </button>
    </model-viewer>
    <!-- 船只模型viewer,默认隐藏 -->
    <model-viewer id="canoe-viewer" src="Canoe.glb" poster="Canoe.webp" shadow-intensity="1" ar camera-controls touch-action="pan-y" alt="Canoe" class="hidden-model">
    </model-viewer>
    <!-- 其他模型的viewer按同样方式添加 -->
</div>

<div class="slider">
    <div class="slides">
        <button class="slide selected" onclick="toggleModel('chair-viewer', this)">
            <img src="Chair.webp" alt="Chair">
        </button>
        <button class="slide" onclick="toggleModel('canoe-viewer', this)">
            <img src="Canoe.webp" alt="Canoe">
        </button>
        <!-- 其他模型按钮按同样方式添加 -->
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<script src="script.js"></script>
</body>
</html>

修改后的script.js代码

// 存储所有模型viewer的引用
const modelViewers = {
    'chair-viewer': document.querySelector('#chair-viewer'),
    'canoe-viewer': document.querySelector('#canoe-viewer'),
    // 其他模型viewer的引用按同样方式添加
};
// 记录当前激活显示的模型
const activeModels = new Set(['chair-viewer']);

// 切换模型显示/隐藏的函数
window.toggleModel = (viewerId, element) => {
    const viewer = modelViewers[viewerId];
    if (activeModels.has(viewerId)) {
        // 隐藏模型
        viewer.classList.add('hidden-model');
        activeModels.delete(viewerId);
        element.classList.remove('selected');
    } else {
        // 显示模型
        viewer.classList.remove('hidden-model');
        activeModels.add(viewerId);
        element.classList.add('selected');
    }

    // 同步所有激活模型的相机参数,解决缩放/视角不一致问题
    const mainViewer = modelViewers[Array.from(activeModels)[0]];
    if (mainViewer) {
        const cameraPosition = mainViewer.getCameraPosition();
        const cameraTarget = mainViewer.getCameraTarget();
        activeModels.forEach(id => {
            if (id !== mainViewer.id) {
                modelViewers[id].cameraPosition = cameraPosition;
                modelViewers[id].cameraTarget = cameraTarget;
            }
        });
    }
};

// 阻止XR模式下的默认选择行为
document.querySelector(".slider").addEventListener('beforexrselect', (ev) => {
    ev.preventDefault();
});

方案二:合并模型+控制节点可见性(更高效)

如果允许修改模型文件,将所有需要切换的模型合并为一个GLB/GLTF文件(可以用Blender等工具合并),然后通过model-viewer的API访问模型内部节点,控制节点的可见性。这种方案性能更好,不会有多个viewer的相机同步问题。

HTML代码

<!doctype html>
<html lang="en" dir="ltr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
    <title>Model-Viewer</title>
</head>
<body>
<model-viewer id="combined-viewer" src="combined-models.glb" poster="combined.webp" shadow-intensity="1" ar camera-controls touch-action="pan-y" alt="Combined Models">
    <button slot="ar-button" id="ar-button">
        View in your space
    </button>
    <button id="ar-failure">
        AR is not tracking!
    </button>
    <div class="slider">
        <div class="slides">
            <button class="slide selected" onclick="toggleModel('chair')">
                <img src="Chair.webp" alt="Chair">
            </button>
            <button class="slide" onclick="toggleModel('canoe')">
                <img src="Canoe.webp" alt="Canoe">
            </button>
            <!-- 其他模型按钮 -->
        </div>
    </div>
</model-viewer>

<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<script src="script.js"></script>
</body>
</html>

script.js代码

const modelViewer = document.querySelector('#combined-viewer');
// 存储模型节点的引用
const modelNodes = {};

// 模型加载完成后获取内部节点
modelViewer.addEventListener('model-loaded', () => {
    const scene = modelViewer.model.scene;
    // 假设合并后的模型中,椅子节点名称为"chair",船只节点名称为"canoe"(需和模型内节点名一致)
    modelNodes.chair = scene.getObjectByName('chair');
    modelNodes.canoe = scene.getObjectByName('canoe');
    // 默认显示椅子
    if (modelNodes.chair) modelNodes.chair.visible = true;
    if (modelNodes.canoe) modelNodes.canoe.visible = false;
});

// 切换模型可见性的函数
window.toggleModel = (modelName) => {
    const node = modelNodes[modelName];
    if (!node) return;
    node.visible = !node.visible;
    // 更新按钮选中状态
    const button = document.querySelector(`.slide[onclick="toggleModel('${modelName}')"]`);
    if (button) {
        button.classList.toggle('selected', node.visible);
    }
};

document.querySelector(".slider").addEventListener('beforexrselect', (ev) => {
    ev.preventDefault();
});

方案说明

  • 方案一适合不想修改模型的场景,通过多viewer叠加实现多模型显示,相机同步解决缩放问题。
  • 方案二更适合性能要求高的场景,单viewer加载组合模型,直接控制节点可见性,无相机同步问题。

内容的提问来源于stack exchange,提问作者Mahmoud Khoravi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 06:57:01