← ALL NOTES
METHOD·2026.08·5 MIN READ
模型缩放时的相机补偿动画设计
3d-visualization3d-design
问题

Scanat 支持修改模型的缩放比例。比例变化后,模型尺寸会立即更新,同时相机也需要调整距离,保证用户仍然能看清模型。

PRD 对这件事的描述很简单:模型比例修改后,视角自动调整。

真正需要设计的是运动过程本身:模型和相机是同时变化,还是先后变化?相机什么时候开始移动?持续多久?使用什么缓动?这些问题很难通过静态设计稿确定,也没有一个可以直接套用的标准参数。

因此这次没有先把时间和缓动写死,而是先做一个可运行的 demo,把停顿时长和动画时长暴露成实时参数,用实际运动来比较不同方案。

动画的任务

模型缩放后的相机移动,本质上是一种被动补偿。

用户真正发起的操作是"修改模型比例"。模型尺寸变化是主要反馈,相机移动只是随后发生的视野修正。如果两者完全同步,用户很难分辨模型本身发生了什么变化;如果相机反应太晚,又会产生明显断层。

这和"重置视角"一类主动相机动画不同。后者来自用户明确的导航指令,可以立即开始移动;缩放后的相机补偿则需要先让模型变化被感知,再进入运镜。

维度模型缩放后的相机补偿重置视角
触发方式模型状态变化后的被动反应用户主动发起导航
开始前状态通常静止可能仍带有拖拽惯性
是否需要停顿需要,让用户先看清模型变化通常不需要
缓动重点起点和终点都需要柔和更强调接近终点时的减速

这里最重要的不是记住某条缓动曲线,而是先判断:这个相机运动为什么发生。 触发原因不同,时间关系和手感也应该不同。

最终方案

最终采用三段式时序:模型立即缩放 → 停顿 0.4 秒 → 相机移动 1.5 秒。

停顿的作用是把两个变化拆开。用户先看到模型尺寸变化,再看到相机为新的尺寸重新调整距离。这样运动关系更容易理解,也减少了模型和视角同时变化带来的视觉混乱。

相机只沿当前观察方向调整与模型的距离,不改变观察角度。模型放大时相机后退,模型缩小时相机前移。整个过程保持为"同一视角下的推拉",而不是一次新的镜头切换。

缓动使用 easeInOutCubic。相机从静止状态开始,又在静止状态结束,因此起点和终点都需要平缓过渡。最初测试过 ease-out,但起步速度偏快,运动开始时会有明显的突兀感。

用原型调参数

这次 demo 最有用的部分并不是复现最终动画,而是把原本写在代码里的数字变成了可以直接调整的参数。

停顿时长和相机动画时长都做成实时滑块。修改后可以立即重新触发动画,不需要反复改代码、刷新页面。

HTML
<input type="range" id="hold" min="0" max="2" step="0.1" value="0.4" />
<input type="range" id="dur" min="0.4" max="3" step="0.1" value="1.5" />

0.4 秒和 1.5 秒都不是设计开始时就确定的数值,而是在连续比较不同组合后得到的结果。

对于这类依赖运动感受的参数,原型可以把讨论从"我觉得 0.5 秒可能比较合适"变成直接比较 0.3、0.4、0.5 秒的实际差异。参数没有脱离场景单独被决定,而是在完整运动里被判断。

相机路径

相机目标位置根据模型缩放比例调整距离:

JAVASCRIPT
const ratio = newScale / oldScale;
const toPos = camera.position.clone().multiplyScalar(ratio);

这里的重点是保持当前观察方向,只改变距离。比例变为 2x 时,相机沿原来的方向后退到相应距离;观察角度不发生变化。

这种路径在视觉上更稳定,也更符合"相机只是补偿模型尺寸变化"的角色。如果同时改变方向或重新计算观察角度,相机动画本身会变成新的视觉事件,反而削弱对模型变化的理解。

实现备注

三段式时序。 Demo 中使用简单的延时和动画状态实现模型变化、停顿和相机移动:

JAVASCRIPT
setTimeout(() => {
  pivot.scale.setScalar(pendingScale);
  const fromPos = camera.position.clone();
  const toPos = camera.position.clone().multiplyScalar(ratio);

  setTimeout(() => {
    anim = {
      fromPos,
      toPos,
      start: performance.now(),
      dur: durMs
    };
  }, holdMs);
}, loadMs);

缓动。

JAVASCRIPT
function easeInOutCubic(t) {
  return t < 0.5
    ? 4 * t * t * t
    : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
经验总结
  1. 先判断相机动画的角色。 主动导航和状态变化后的被动补偿,不应该直接复用同一套时间和缓动参数。
  2. 连续变化不一定要同时发生。 当两个动画分别承担"主要变化"和"补偿反馈"时,短暂停顿可以帮助用户理解它们之间的因果关系。
  3. 尽量保持观察方向稳定。 如果相机只是为了补偿模型尺寸,优先调整距离,而不是同时改变视角。
  4. 动效参数要在运动里判断。 对停顿、时长、缓动这类没有固定答案的参数,可调原型比静态说明更有效。
关于动效原型

这次 demo 没有发现新的交互 bug,它解决的是另一类问题:在真正看到动画之前,很难判断某一种运动是不是合适。

静态稿可以表达动画前后的状态,文字可以描述时长和曲线,但两者都不能完整传达运动本身。尤其是停顿、速度变化和前后两个动作之间的关系,只有运行起来以后才能判断。

因此在这类任务里,原型不只是展示方案,也是一种设计工具。它把难以描述的"手感"变成可以直接观察和比较的对象。

CONTINUE READING
从 Figma 组件属性到代码 Props2026.09Figma 设计系统的工程化落地2026.09