diff --git a/packages/model3d-viewer/src/scene.ts b/packages/model3d-viewer/src/scene.ts index 440a57333..bfc11ce3e 100644 --- a/packages/model3d-viewer/src/scene.ts +++ b/packages/model3d-viewer/src/scene.ts @@ -272,25 +272,34 @@ export async function createModel3dViewerScene( abortIfNeeded(); const bounds = new THREE.Box3().setFromObject(model3d); - const size = measureViewport(); - const fitBox = { - min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z }, - max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z }, - }; - const fitCenter = computeModel3dViewerBoxCenter(fitBox); - const fitSolver = createModel3dViewerFitSolver({ - center: fitCenter, - aspect: size.aspect, - fovDegrees: MODEL3D_VIEWER_CAMERA_FOV_DEGREES, - fallbackRadius: computeModel3dViewerBoxRadius(fitBox), - }); - feedModel3dViewerFitSolver(model3d, (x, y, z) => fitSolver.add(x, y, z)); - const framing = computeModel3dViewerFraming({ - box: fitBox, - aspect: size.aspect, - fovDegrees: MODEL3D_VIEWER_CAMERA_FOV_DEGREES, - distance: fitSolver.resolve(), - }); + const viewport = measureViewport(); + + /** + * 按给定宽高比重算取景。容器在隐藏面板里挂载时首帧量不到尺寸,只能先按方形兜底, + * 等容器真正有尺寸后必须用真实比例重算,否则模型会按方形取景、被裁掉或显得过小。 + */ + function computeFraming(aspect: number) { + const fitBox = { + min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z }, + max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z }, + }; + const fitSolver = createModel3dViewerFitSolver({ + center: computeModel3dViewerBoxCenter(fitBox), + aspect, + fovDegrees: MODEL3D_VIEWER_CAMERA_FOV_DEGREES, + fallbackRadius: computeModel3dViewerBoxRadius(fitBox), + }); + feedModel3dViewerFitSolver(model3d, (x, y, z) => fitSolver.add(x, y, z)); + return computeModel3dViewerFraming({ + box: fitBox, + aspect, + fovDegrees: MODEL3D_VIEWER_CAMERA_FOV_DEGREES, + distance: fitSolver.resolve(), + }); + } + + let framing = computeFraming(viewport.aspect); + let hasFramedViewport = viewport.width > 0 && viewport.height > 0; function measureViewport(): { width: number; @@ -342,6 +351,17 @@ export async function createModel3dViewerScene( if (typeof ResizeObserver !== 'undefined') { resizeObserver = new ResizeObserver(() => { applyViewport(); + if (disposed || hasFramedViewport) { + return; + } + // 首次拿到非零尺寸:按真实比例重算取景并复位,用户此后的缩放不再被覆盖。 + const current = measureViewport(); + if (current.width === 0 || current.height === 0) { + return; + } + hasFramedViewport = true; + framing = computeFraming(current.aspect); + resetView(); }); resizeObserver.observe(container); }