如何在model-viewer中同时显示多3D模型并解决缩放问题?
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
相关产品推荐
相关产品推荐

