ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

实宽高与虚宽高:视觉还原中对齐约束的几何真相

实宽高与虚宽高:视觉还原中对齐约束的几何真相 做视觉还原和布局开发的人多多少少都遇到过这么一件事设计稿里明明是一个 100×100 的卡片按测量工具一量边界框也是 100×100可单独看视觉呈现时它看起来是 104×104因为多出来的是阴影外溢和描边。反过来了的也有——一段文本在图层面板里显示高度 48px视觉上却只有 34px 高垂直居中怎么弄都差一截。这就是 RGA 系列一直在聊的核心问题几何信息在从设计引擎进入布局引擎的过程中存在一套实宽高和虚宽高的对应关系。前三篇里我们聊了整体框架和基本测量原理这篇专门拆开实宽高与虚宽高这对概念以及它们如何决定对齐约束的行为。这套东西搞不清楚你用自动布局也好手写约束也好永远是差那几像素在那边挠头。1. 先搞清楚为什么同一个元素会产生两套宽高1.1 一次真实的还原现场测量尺寸与布局尺寸对不上先说个最常见的场景。设计稿里有个按钮Figma 里面选中它右侧面板显示宽 120、高 40。你兴冲冲地在代码里写了个 120×40 的容器把文本放进去一渲染发现不对按钮的底部阴影被裁剪了或者按钮比设计稿上看的要小一圈。再换个方向。一个带发光效果的图标设计稿上看起来占 60×60但选中后的边界框只有 44×44。你按 44×44 去做了布局结果图标和旁边的文字挤在一起呼吸感全没了。这两类情况本质上都是一个问题一个元素在画布上的视觉占地面积和它的布局占位尺寸并不是同一个东西。我把布局占位尺寸称为实宽高把视觉占地面积称为虚宽高。听起来有点绕但用一句话就能记住实宽高是别人为它让出多少空间虚宽高是它实际在视觉上占了多少画面。1.2 实宽高是布局的骨架虚宽高是视觉的皮肉实宽高对应的是元素的布局盒子。在代码里这就是 CSS 的 width/height是 Flexbox 和 Grid 布局计算时真正使用的尺寸。所有兄弟元素的对齐、换行、间距分配依据的都是它。它是不可妥协的、参与几何计算的尺寸我把它理解成整个布局的骨架——骨头的长度决定了关节的位置。虚宽高对应的是元素的渲染盒子。它等于实宽高加上所有视觉效果的扩展量阴影的模糊半径、描边的宽度、光晕的扩散范围、字体的隐式留白。这部分不参与任何布局计算纯粹是画出来的但人眼看到的恰恰是它。它是皮肉决定了这个人看起来胖还是瘦、精神还是疲惫。问题在于绝大多数设计稿里设计师用的是皮肉在摆画面而你写代码时只有骨架能参与计算。于是你必须在两者之间做翻译。翻译得不好就是各种对不齐、被裁剪、间距失衡。1.3 三类最常见的虚宽高来源阴影溢出、描边扩展、字体的隐形留白我总结了三种最容易产生虚宽高、又最容易被忽略的来源。阴影溢出。一个卡片加了box-shadow: 0 4px 12px rgba(0,0,0,0.1)视觉上它的底部至少多出了 12px 的模糊区。如果你在布局里没有给卡片留出这段空间阴影就可能和下面的元素重叠甚至被父容器裁掉。问题在于很多设计工具里阴影不占边界框尺寸选中后图层框依然是原来的大小这很容易骗过刚接触的人。描边扩展。一个 100×100 的圆描边宽度 4。描边在默认情况下是以路径为中心线向两侧扩展的所以视觉上它占了 104×104。代码里的 border 也一样border-box 和 content-box 的计算基准是两套完全不同的模型。设计稿里如果描边是居中的那么你按 border-box 写时实际内容区域会缩小按 content-box 写时元素总宽高会变大。字体的隐形留白。这个最隐蔽。一个 40px 字号的文本在 Figma 里设置行高为 60px 时文本框的高度就是 60px。但字符本身的视觉高度可能只有 40px 多一点剩下的 20px 是空白的。如果你的设计师常用行高整数来约束文本高度那么文本组件就会出现实宽高 60虚宽高 40的大幅偏差。文本在容器里怎么居中都看起来偏上或偏下根子就在这。2. 字体行高才是虚宽高的第一大坑2.1 行高不是字体大小行高的一半是虚的很多前端同学刚接触设计稿时对文本的理解都是字多大就是多大。我刚开始也是。后来被一个 60px 高的文本组件折磨了一个下午才发现文本的行高是一个独立的维度与字号无关而且行高在渲染时是对半分配的——上半行距和下半行距各占一半。举个具体的例子。字号 40px行高 60px那么行高与字号的差值就是 20px。这 20px 平均拆成 10px 放在文字上方、10px 放在文字下方。也就是说单独一个文本组件它的实际视觉占位是 40px 高但边界框是 60px 高上下各多了 10px 的透明留白。这就是为什么你拿着文本组件在容器里做垂直居中时文字看起来永远不在中间因为你的对齐计算用的是 60px 的实宽高而人眼感知的是 40px 的视觉主体。上下各 10px 的空白被加分到了居中的计算里结果文字主体自然往下或往上偏移了。2.2 对齐时该用实宽高还是虚宽高段落级对齐全用实宽高那是不是所有场景都要去修正这个偏差我实践下来的结论是段落级的对齐一律以实宽高为准。所谓段落级就是一个多行文本块作为一个完整单元参与上下排布。此时文本块的实宽高决定了它在文档流里的占位后面元素的上下间距、换行位置都依赖这个值。如果你为了视觉准去压缩文本块的高度后续所有元素的排布都会跟着错位那是灾难。单行文本和图标混排时则恰恰相反应该用视觉高度来对齐。图标通常有自己的垂直留白文本有行高留白你要做的是让两者的视觉中心在同一水平线上而不是让它们的边界框中心对齐。这个区别非常微妙但又是日常开发中差一像素症的最大来源。我通常的做法是先按实宽高完成整体布局然后单独对混排行做一次视觉微调把文本的行高适度减小或者用line-height覆盖为固定值再检查图标与文字的视觉中心。这个顺序别反先整体再局部先几何再视觉。2.3 文本组件的测量插曲Figma 里文本高度为什么偏高如果你用 Figma 做设计稿还会遇到一个平台特性Figma 对文本组件的边界框按行高计算而不是按字体的 ascender/descender 计算。这意味着一个默认行高例如 1.2 倍的文本它的边界框在软件里也会比字符的视觉区域高出一截。这带来一个连锁反应设计师在设计稿里如果依赖 Figma 的图层边界框去摆放元素间距那么实际渲染到浏览器里字符之间的视觉间距会比设计稿更紧凑因为浏览器不会把行高的上半部和下半部完全按相同比例渲染——不同字体甚至不同浏览器对基线以上和基线以下的分配都不完全一致。所以如果你在做跨端还原我建议你养成一个习惯拿到设计稿后先看文本的行高系数如果设计师用的是 1.3 以上在代码里大概率需要手动压到 1.01.2。这本质上是把虚宽高往实宽高方向压缩让文本的视觉面积与占位面积更接近。特别是做按钮、标签这类紧凑组件这个动作几乎必做。3. 对齐约束约束的是实宽高看到的却永远是虚宽高3.1 左对齐、居中、拉伸背后真正约束的坐标系对齐约束比如左对齐、右对齐、水平居中、垂直居中、拉伸撑满它们约束的对象从来都是实宽高。CSS Flexbox 里的justify-content、align-itemsFigma 自动布局里的Hugs、Fixed、Fill底层都是拿布局盒子在做几何计算。但在设计阶段你看到的最终画面却是虚宽高渲染后的结果。于是有了这样一个错位约束引擎说我已经把这个元素水平居中了但因为有阴影不对称、描边居中、文本行高多余视觉上它并没有居中。举个我实际处理过的案例。一个 120px 宽的按钮右侧有一块 8px 的阴影外溢左侧干干净净。设计师让按钮在 300px 的容器里水平居中。约束计算时按钮左边缘和右边缘距离容器各是 90px数学上完美居中。可渲染出来后因为右侧那 8px 阴影视觉上按钮的视觉重心偏左了约 4px人眼看着就是歪的。3.2 当视觉居中和约束居中打架时怎么办这个问题从需求上分两种情况如果你的设计稿不允许阴影外溢那在组件设计阶段就要把阴影改用内阴影或者把阴影扩散控制在边界框内。如果设计上就是需要外阴影那对齐时就要在约束之外再做一次视觉补偿——比如给按钮的 margin 右侧加 4px或者把按钮整体向左偏移 4px。我比较推荐的做法是做一个补偿变量在设计系统里给每个带外阴影的组件定义一个visual-offset属性数值来自阴影的水平偏移与模糊半径的一半。布局约束依然按实宽高来渲染阶段再用这个变量做一次微调。这样代码结构是清晰的也不会因为阴影调了一点数值就全局乱套。最怕的是直接在布局里叠写死 margin 来修对齐。头两天看着是齐了等设计师把阴影模糊半径从 12 改成 20你那个写死的 margin 就是一颗定时炸弹。用变量抽象出补偿逻辑哪怕数值变了计算关系不变。3.3 约束条件里的溢出与收缩撑满与限宽的关系对齐约束还有一个容易被忽视的维度拉伸stretch和 Fill 模式下的尺寸行为。当一个元素被设为撑满容器宽度时约束引擎会把它的实宽高拉满到容器内边界。此时如果该元素有阴影或描边虚宽高就会超出容器边界轻则视觉上溢出重则被overflow: hidden裁掉。这里需要明确优先级约束是强制的视觉尺寸是跟随的。所以当撑满和视觉溢出发生矛盾时你能动的只有视觉层的参数而不是约束本身。实际操作上就三条路给容器留出内边距把阴影挪进边界框内或者让元素不撑满而是居中对齐。另外在 Flex 布局里有一个常见误区flex: 1让子元素撑满父级但没给子元素设置min-width: 0导致内容溢出。这不是虚宽高直接造成的但和实宽高被内容撑开是同源问题——当你压缩一个元素的行宽时内容文本或图片的固有尺寸会反过来影响实宽高的计算。RGA 分析里这种情况叫内容态对几何态的干涉后面我们专门展开这里先标记一下。4. 让两者协同工作的几条实操铁律4.1 铁律一阴影和高斯模糊必须要有出血位所谓出血位就是容器/父级为子元素的阴影外溢预留的透明空间。在设计稿里通常表现为卡片上下多出一段空白在代码里则是父容器的 padding 大于等于阴影的外溢半径。我的习惯是阴影的模糊半径记为b水平偏移记为x垂直偏移记为y。那么四个方向需要的出血位大约是上max(0, -y) b/2下max(0, y) b/2左max(0, -x) b/2右max(0, x) b/2当然这是近似值阴影的实际可见边缘还受颜色透明度影响但作为预订的 padding 基准足够。给足了出血位虚宽高就不会被裁剪后续对齐计算也不会被阴影干扰。4.2 铁律二描边放在内侧虚宽高才不会扩散CSS 里box-sizing: border-box配合border本质上就是把描边画在实宽高内部虚宽高与实宽高保持一致。这是我从做设计系统开始就坚持的全局设置。如果你手头的设计稿里大量使用了描边居中的视觉样式那么在还原时务必和设计师沟通让他们把描边改成朝内或者你直接按 border-box 适配。因为居中描边在代码里难以精确复刻不同的渲染引擎对 outside line 的处理不一致最后就是虚宽高在不同浏览器里忽大忽小。Sketch 和 Figma 的图层面板里都有描边位置的选项内、居中、外。凡是参与自动布局的组件一律选内。凡是不参与布局的纯粹装饰才允许用居中或外部描边且它们周围必须预留足够的间距。4.3 铁律三文本组件的行高需要修正为1.0~1.2这个前面已经反复提到了放在铁律里再强调一次。插画、按钮、标签、选项卡所有这类视觉组件里包含文本的我都会把文本行高压到 1.01.2而非使用设计稿里可能较大的默认行高。具体数值怎么定我的经验是从 1.2 开始试。大多数情况下1.2 能很好地平衡文本的可读性和占位紧凑度。如果组件里文字和图标混排我还会把文本的 vertical-align 设为 middle然后微调 line-height。这套操作下来文本带来的虚宽高盈余基本被消掉了百分之八十。需要注意的是段落正文的行高不要乱压。正文要保持可读性行高系数 1.51.8 都是常态这属于内容区域不是组件几何压缩它会影响阅读体验。铁律三只适用于文本作为组件一部分且参与对齐的场景。4.4 铁律四所有对齐判断基于实宽高渲染效果基于虚宽高检查这是我整套 RGA 工作流的最后一道工序。步骤是布局阶段所有约束、间距、排列全部以实宽高为输入渲染阶段打开浏览器 DevTools 的 outline 模式或者用截图对比工具把虚宽高的渲染结果截下来叠加到设计稿上对比时只看视觉差异不做数学分析。这背后的逻辑是模型给的是确定性答案视觉给的是主观判断。如果在模型层就混入虚尺寸你会陷入怎么算都不对的死循环如果在检查层不看虚尺寸你又无法发现 4.14.3 里说过的各种视觉溢出和偏移。实际执行的时候我个人习惯用 PixelMatch 之类的工具跑一遍对比图然后把差异区域标出来逐个排查。通常排查顺序是阴影出血位 → 描边位置 → 文本行高 → 对齐补偿。百分之九十的差几像素问题都在这四个环节里。5. 一次完整的排查沙盘按钮组为什么总是偏为了让上面这套理论落地我拿一个真实项目里的按钮组案例再走一遍。当时的场景是一个工具条左边一个主按钮右边一个图标按钮两者之间 12px 间隔整体在容器里水平居中。设计师反馈两个按钮整体看着偏右了 3px但无论怎么调布局都调不正。我这个排查分了三步走。第一步检查实宽高。主按钮固定宽高 120×40图标按钮 40×40间隔 12px总占位 172px。容器宽度 400px居中后左右留白各 114px。数学计算没有任何问题。第二步检查虚宽高。打开设计稿的高亮模式发现主按钮右侧有一道 8px 的阴影按钮朝右下方投影底部还有一道 4px 的阴影。于是视觉重心右移了约 4px图标按钮干净无阴影。那么整体视觉居中点被往右拽了约 2px 左右正好对上设计师说的偏右 3px。第三步检查文本行高。再细看主按钮的文字line-height 从默认的 1.5 被压缩到 1.2 渲染按钮看起来比设计稿高约 4px 的视觉留白少了所以按钮高度上的视觉占比也变了导致垂直方向也有轻微偏移。把行高压到 1.0 后因为按钮文本是单行的大字号视觉占比更接近设计稿。最后给的修复方案主按钮的阴影改小一点模糊半径从 12 降到 8同时把右侧出血位预留 4px并在容器 padding 层面为阴影预留 8px 空间。重新跑对比图偏移从 3px 降到了 0.6px 以内顺利通过。这个案例说明三件事第一虚宽高影响的是最终的人眼判定布局算法并不知道也不 care第二排查时要先确认实宽高的数学正确性否则你会在错误的基础上浪费大量时间第三修正手段优先选降低虚宽高扩散而不是在实宽高上打补丁。6. 继续深挖前先记住这几个手感和判断做多了 RGA 分析之后我发现实宽高与虚宽高的关系本质上是对设计的意图和渲染的物理之间的一次翻译。翻译得好不好不是看你会不会用 Flexbox而是看你能不能准确分辨出哪个环节应该用哪个尺寸。我给自己总结了几个判断分享出来供你参考凡是参与排列的必用实宽高。排列包括自动布局、flex、grid、绝对定位的坐标计算。凡是参与观感的必查虚宽高。观感包括阴影溢出、描边占位、文本视觉居中、圆角内外比例。如果实宽高和虚宽高发生冲突优先改虚宽高。例如把阴影内移、把描边内画、压缩文本行高都是低成本高收益的手段。如果冲突无法通过改虚宽高解决比如设计师坚持大阴影外发光那就为虚宽高预留空间用 padding 或透明占位元素把布局撑开。最后也是最重要的每次做组件先问自己一句——这个视觉效果是骨骼的一部分还是皮肉的一部分想清楚这个实虚之争就消失了。RGA 系列到这里实宽高、虚宽高与对齐约束的核心内容就梳理完了。紧接着第四篇我打算把内容态对几何态的干涉正式拉出来讲包括文本溢出换行、图片固有尺寸、自适应撑开等场景下的几何二次计算。这些问题只聊纯对齐是解决不了的必须把内容当做一个变量引入模型。到时候我们再看它和今天的实虚宽高是怎么联动的。
返回列表