viewBox 摄像机:推拉与聚焦
SVG 的 viewBox="x y w h" 决定「把世界坐标的哪一块矩形,铺满当前视口」。动这四个数就等于移动一台摄像机:改
、
是平移,改
、
是推拉,令矩形对准某个形状就是聚焦。内容一笔未动,只是取景框在变。
1 · 四个数的补间
被补间的「值」是取景框的四个标量,和补间一个小球的位置没有任何不同。动画引擎对「值」是什么一无所知,它只负责把 from 到 to 按进度混合——同一台引擎接到 dashoffset 就是描边,接到 transform 就是移动元素,接到
viewBox 就是摄像机。
2 · 推拉由宽高承担
与
变小意味着取景范围变窄,同样的视口装下更少的世界,看上去就是放大。这与 transform: scale() 的方向相反,是这套坐标最容易记反的一处。
警示 · 四个分量各自线性插值,得到的不是视觉上的匀速推拉。人眼对缩放的感知接近对数——从 推到 ,前半段(600 到 400)看起来慢、后半段(400 到 200)看起来快,因为后者的比例变化更大。要视觉上匀速, 与 该走指数插值 而非线性。本页用线性是有意的:它让「四个数各做一次普通插值」这件事保持透明,而真正做地图缩放时应当换成指数。
另一处容易漏的是长宽比。四个预设取景框的宽高比都接近
,与视口一致,所以 preserveAspectRatio="xMidYMid meet" 不会额外留边。若目标框比例不同,meet 会在短边补上留白——插值过程中留白的宽度也在变,画面会有一种说不清的呼吸感。做聚焦时通常先把目标框按视口比例扩一下再补间。
const from = current; // [x, y, w, h]
const to = [44, 30, 200, 160];
new Animation({
keyframes: [0, 1],
duration: 1100,
ease: 'easeInOut',
onUpdate: (g) => {
const box = from.map((f, i) => f + (to[i] - f) * g);
svg.setAttribute('viewBox', box.join(' '));
},
});
取景框回答「看哪里」;让一个物体在世界里行进是「谁在动」,见沿路径运动。两者可以叠加——摄像机跟随物体,即每帧把 viewBox 对准它的当前坐标。