容器首帧无尺寸时按真实比例重算取景

- 取景原先只在加载完成时按 measureViewport() 的宽高比算一次,隐藏面板里挂载时只能拿到方形兜底值
- 抽出 computeFraming(aspect),ResizeObserver 首次量到非零尺寸后按真实比例重算并复位一次,之后不再覆盖用户的缩放
This commit is contained in:
2026-09-21 15:17:11 +08:00
parent f732555a44
commit 3e426a1006
+39 -19
View File
@@ -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);
}