做界面这一行时间长了,会发现一个挺有意思的现象:很多人能把 Figma 里的图层画得漂漂亮亮,一到真实项目里就露怯——按钮点下去没反应,表单报错不知道哪错了,手机上文字挤成一团,改一个主色全站配色崩掉。问题不在审美,在于对 UI 的核心概念没有真正吃透。UI 不是"画得好看",它是一套关于信息如何被组织、状态如何被表达、操作如何被反馈的工程化语言。这篇文章想做的,就是把 UI 设计里那些看起来零散、实际上有严密内在逻辑的核心概念掰开揉碎讲一遍:视觉四要素怎么定、组件为什么要拆、状态为什么比样式更重要、设计稿怎么才能顺利落地成代码。适合刚入行的设计新人、需要跟设计对接的前端、以及做独立产品想自己搞定界面的开发者。不堆术语,尽量用项目里的真实场景说话,能直接抄的部分我会把参数和代码贴出来。
1. 先把 UI 这件事说清楚:它到底在解决什么问题
1.1 UI、UX、视觉设计三者最容易混淆的边界
刚接触这个领域的人,几乎都会在"UI 和 UX 有什么区别"这个问题上卡一下。行业里常见的解释是"UX 是体验,UI 是界面",这话没错但太虚,实际干活时根本用不上。我用一个具体的例子来说明:用户要在某 App 里绑定一张银行卡。UX 关心的是——这个流程要几步?能不能扫卡自动识别?输错了是回到第一步还是只清空那一栏?要不要在中途保存草稿,让用户下次接着填?UI 关心的是——输入框多高、圆角多大、卡号每四位要不要空格、错误提示用什么颜色、按钮放左还是放右、禁用态长什么样。
你看出区别了吧:UX 决定"流程长什么样",UI 决定"这个东西长什么样、每个瞬间呈现什么状态"。而视觉设计(Visual Design)是 UI 里偏感知的那一层,管色彩、字体、图形风格。三者是嵌套关系,不是并列关系。很多团队招聘写"UI/UX 设计师",其实要的是一个既能画流程又能落地视觉的人,但这两件事的能力模型差别挺大,前者偏逻辑推演和用户研究,后者偏对比例、节奏、细节的敏感度。
我个人的判断是:入门阶段先死磕 UI,因为它的反馈最直接——东西对不对,截图一看就知道;UX 的判断需要数据和时间来验证,新人很难快速建立手感。等 UI 的基本功扎实了,再往回推流程,会顺很多。反过来先学 UX 再去补视觉,容易陷入"流程画得很完整但界面没法看"的尴尬。
1.2 一个界面从需求到上线,UI 负责哪几段
把一次完整的交付拆开看,UI 的介入点其实比很多人想的前后都长。需求评审阶段,UI 就该在场,因为很多需求在文字阶段是模糊的,"用户能看到自己的订单"这句话,背后藏着列表为空怎么办、加载中显示什么、超过一百条要不要分页、状态有几种、每种什么颜色。这些如果等到出图时才问,返工成本会翻好几倍。
接着是信息架构和低保真,画方块和箭头,确认页面之间的跳转关系。这一步很多人跳过,直接开高保真,结果做到一半发现少了个页面。然后才是视觉稿,定栅格、定色板、定字号阶梯。再之后是标注交付、开发实现、走查验收、上线后根据反馈微调。
我见过最省事的做法是:设计在出高保真之前,先跟开发对一次"技术可行性",把动效、滚动、吸顶、下拉刷新这些交互的实现成本摸清楚。有些效果在设计工具里一拖就有了,真机上做出来性能掉一半,这种信息如果到走查阶段才发现,双方都很难受。所以别把 UI 理解成"设计稿产出环节",它是一条贯穿始终的线。
1.3 入门阶段最该建立的判断标准
新手最常见的问题不是不会用工具,而是没有判断标准。看到一个界面,说得出"好看"或"不好看",但说不出为什么。我建议入门时先建立三条硬标准,用它们去审视自己的每一稿:
第一,信息有没有主次。一屏里用户第一眼该看到什么?如果三个元素一样大一样重,用户的眼睛就会乱跑。第二,状态有没有穷尽。默认、悬停、点击、禁用、加载、成功、失败、空数据,这八种状态你都画了吗?漏一个,开发就得自己猜。第三,间距有没有系统。是随手拖的 13px、17px、22px,还是从一套固定的数值里取的?前者改起来是灾难,后者改一个变量全站跟着动。
这三条标准听起来朴素,但真能做到的人不多。我审过不少稿子,光"空状态"这一项就能筛掉一半——列表没数据时页面一片白,用户以为是加载失败。这种细节在演示时看不出来,一上线全是投诉。
2. 视觉四要素:颜色、字体、间距、层级
2.1 颜色:从主色到语义色的分层思路
颜色最容易犯的错,是把"配色"理解成"挑几个好看的色"。真实的项目里,颜色是一套有层级的系统,我一般分成四层来定。
第一层是品牌主色,通常一个,用于主要操作按钮、选中态、关键强调。第二层是辅助色,一到两个,用于次级强调或分类区分。第三层是语义色,成功、警告、错误、信息,这四个是刚需,不能省。第四层是中性色阶,从纯白到纯黑中间拉出 8 到 10 级灰度,承担文字、边框、背景、分割线所有的活儿。
这里有个关键点很多人忽略:语义色不能直接用主色替代。有些项目为了省事,把错误提示做成品牌红,结果品牌色一调整,错误提示跟着变,用户对"红色=出问题了"的认知就被破坏了。语义色应该独立于品牌色存在,即使品牌换色,红黄绿蓝的语义保持不变。
另外一个实操细节:文字颜色不要用纯黑#000。纯黑在白色背景上对比度过高,长时间阅读眼睛容易累,而且会让整个界面显得生硬。我通常用#1A1A1A到#333333这个区间做主文字,次要文字用#666左右,辅助说明用#999,禁用态再浅一档。这套值不是随便定的,是大量阅读场景下比较舒服的区间。
如果你在做前端,强烈建议一开始就把颜色定义成变量,而不是散落在各个文件里:
:root { /* 中性色阶 */ --color-text-primary: #1A1A1A; --color-text-secondary: #666666; --color-text-tertiary: #999999; --color-text-disabled: #C0C0C0; --color-border: #E5E5E5; --color-bg-page: #F7F7F8; --color-bg-card: #FFFFFF; /* 语义色 */ --color-success: #1F9D55; --color-warning: #C77700; --color-error: #C62828; --color-info: #1565C0; /* 品牌色 */ --color-brand: #2F5BEA; --color-brand-hover: #2449C4; --color-brand-active: #1B3898; }这么做的好处,改主题时只动一处。我做过一个需要支持多套配色的项目,前期偷懒写死了颜色,后期改主题花了整整两天扫全站,这个教训值一提。
注意:语义色的饱和度别拉太满。纯正的红
#FF0000用在错误提示上会很刺眼,稍微压暗压灰一点,观感会舒服很多,也更符合专业感。
2.2 字体:字号阶梯与行高的计算逻辑
字号最忌讳"每个页面单独定"。今天这个卡片标题 18px,明天那个弹窗标题 17px,最后整个产品看起来像几个人拼出来的。正确的做法是先定一套字号阶梯,所有地方都从阶梯里取值。
阶梯怎么定?我一般按 1.2 到 1.25 的倍率往上走。以 14px 为正文基准,往上 16、18、20、24、30、36,往下 12、10。层级不用太多,移动端 5 到 6 档足够,桌面端可以到 8 档。档位太多反而没人记得住,最后还是随手填。
行高这块有个经验公式:正文行高取字号的 1.5 倍左右,标题取 1.2 到 1.3 倍。原因是正文需要连续阅读,行距大一点眼睛不容易串行;标题通常只有一行,行距太大会显得松散。14px 正文配 22px 行高,16px 配 24px,这是一个比较稳的起点。
字重也别滥用。一个界面里出现四种以上字重,视觉上就乱了。常规做法是三档:正文 regular(400)、强调 medium(500)、标题 semibold 或 bold(600/700)。数字场景(价格、数据面板)可以考虑用等宽数字特性,避免数字跳动时宽度变化引起布局抖动,这个细节在金融、数据类产品里特别重要:
.price, .stat-number { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }还有一点,中英文混排时要注意字体的 fallback。很多西文字体不带中文字形,浏览器会自动回退到系统默认中文字体,结果一段话里中英文粗细、字面大小不统一,很影响观感。稳妥的写法是在 font-family 里把中文字体跟在西文字体后面,让西文用漂亮的字体、中文用系统字体,各司其职。
2.3 间距:8pt 栅格为什么好用
间距是视觉里最容易被低估的一环,但它对"专业感"的贡献可能比颜色还大。你去看那些做得好的产品,元素之间的距离几乎都是同一个基数的倍数,这个基数通常取 4 或 8。取 8 的原因很实际:主流屏幕密度下 8 是能被整除得比较干净的数,1x、1.5x、2x、3x 各种倍率下都不会出现半像素模糊;而 4 作为最小单位,可以覆盖 8 不够用的精细场景。
我常用的间距阶梯是:4、8、12、16、24、32、48、64。对应关系大概是——图标和文字之间 4 或 8,卡片内边距 16 或 24,卡片之间 16,区块之间 32 或 48,页面左右边距移动端 16、桌面端 24 到 32。这套值用熟之后,你几乎不需要思考"这里该留多少",直接从阶梯里挑就行。
为什么这件事重要?因为间距传达的是关系。两个元素挨得近,用户默认它们相关;隔得远,默认它们不相关。表单里标签和输入框之间 8px,两个表单项之间 24px,用户一眼就知道哪几行是一组的。如果所有间距都是随手 15px、20px,分组信息就丢了,用户得靠猜。
注意:8pt 栅格不是死规矩,字号、图标尺寸这些可以不在栅格里。它管的是"布局间距",别把 15px 的字号硬改成 16px 只为了对齐栅格,那就本末倒置了。
2.4 层级:让用户一眼看到该看的东西
层级是四要素里最抽象、也最能体现功力的一项。同样一堆内容,有人排出来一眼就能抓住重点,有人排出来像一锅粥。拉开层级的手段其实就那几样,但组合方式很讲究。
大小对比是最直接的,标题大、正文小。但光靠大小不够,因为字号差距太大会破坏版式平衡。字重对比是第二把刀,同样的字号,加粗就能跳出来。颜色对比是第三把,主文字深、次要文字浅,深浅之间自然分层。空间对比是第四把,重要内容周围留白多一点,自然显得重要。容器对比是第五把,卡片、分割线、底色块,把内容装进不同的盒子里。
真正有效的做法是组合使用,但一屏里不要超过两个强对比手段。比如一个商品卡片:标题用 16px semibold 深色,价格用 18px bold 品牌色,描述用 13px regular 浅灰。这里用了大小、字重、颜色三重对比,但因为描述弱得足够明显,整体还是有层次的。如果描述也用 semibold,那整张卡片就没有主次了。
我判断一稿层级够不够,有个土办法:眯起眼睛看,或者把截图缩小到 30%。如果这时候还能一眼分辨出主标题、正文、按钮,说明层级成立;如果糊成一片,那就是对比不够。
3. 组件化与设计系统:从单页到可复用的关键一步
3.1 原子化拆解:把页面拆成可维护的积木
单页设计做久了会形成一种惯性:每个页面单独画,画完就交付。项目小的时候没问题,一旦页面超过二十个,问题就来了——同一个按钮在五个页面里有五种圆角,同一个输入框有三种高度,改一处得翻遍所有文件。这时候就需要组件化。
原子化拆解的思路很朴素,就是把界面从最小的不可分单位往上组装。最底层是原子:颜色、字号、间距、图标、圆角、阴影这些基础变量。往上一层是分子:按钮、输入框、标签、头像、图标按钮这些单一功能的元素。再往上是组织:搜索栏(输入框+按钮)、表单项(标签+输入框+错误提示)、卡片头部(头像+标题+操作)。然后是模板:整个卡片、整个表单、整个列表项。最后是页面。
拆的时候有个判断标准:这个东西会不会在别的地方出现第二次?会的就做成组件,不会的就先本地画。别一上来就把所有东西都组件化,那样维护成本反而高——改一个只出现一次的卡片,还得进组件库里改,效率更低。我一般的做法是,第一次遇到就本地画,第二次遇到再抽组件。这个"两次法则"能避免过度设计。
3.2 组件状态设计:默认、悬停、聚焦、禁用、加载、错误
这是我认为整个 UI 设计里最见功力的地方,也是最容易被跳过的地方。很多人画组件只画一个"正常态",交付的时候开发只能自己脑补其他状态,最后做出来的东西跟设计稿气质完全不是一回事。
一个完整的按钮组件,至少要覆盖这些状态:默认、悬停、按下、聚焦、禁用、加载中。输入框要覆盖:默认、悬停、聚焦、已填写、禁用、只读、错误、成功。列表要覆盖:加载中、有数据、空数据、加载失败、加载更多中、没有更多了。
| 组件 | 必备状态 | 容易漏掉的细节 |
|---|---|---|
| 按钮 | 默认/悬停/按下/聚焦/禁用/加载 | 加载时宽度是否变化、禁用态是否可点击 |
| 输入框 | 默认/聚焦/已填/禁用/只读/错误/成功 | 错误提示占位是否会顶动下方布局 |
| 下拉选择 | 收起/展开/已选/搜索无结果 | 选项过多时是否滚动、多选如何展示 |
| 列表 | 加载/有数据/空/失败/到底 | 空状态要不要给引导操作 |
| 弹窗 | 打开/关闭/确认中/超长内容 | 内容滚动时头部底部是否固定 |
| 图片 | 加载中/成功/失败/占位 | 不同比例图片如何裁切 |
举一个特别典型的例子:按钮的加载态。很多设计稿里没有这个状态,开发实现时要么把按钮文字改成"加载中",要么转个圈,结果按钮宽度突然变了,整个布局跟着抖一下。正确做法是加载态保持按钮原宽度,内容用同位替换,或者干脆在按钮内左侧加一个固定尺寸的 spinner,文字保留。这种细节不写进设计规范,十有八九会出问题。
再比如错误提示的位置。表单校验失败时,提示文字是挤在输入框下面,还是浮在上面遮挡内容?前者会把下方元素往下顶,导致用户正在点的按钮跑掉;后者会遮住别的输入框。比较稳的方案是预留固定高度的提示位,无论有没有错误都不改变布局。这个决定必须在设计阶段定下来,交到开发手里就晚了。
3.3 设计 Token:让设计与代码说同一种语言
设计 Token 说白了就是把颜色、字号、间距、圆角、阴影这些基础值,抽成一套有名字的变量,设计和代码共用同一份。它的价值在于消除"翻译损耗"——设计稿上写的是"主色 #2F5BEA",代码里写的是--color-brand,双方说的是同一件事,改的时候也只改一处。
Token 一般分两层。基础层是原始值,比如blue-500: #2F5BEA、space-4: 16px。语义层是用途,比如color-brand: {blue-500}、padding-card: {space-4}。组件只引用语义层,语义层指向基础层。这样做的好处是,将来换主题时只需重定向语义层,组件代码一个字都不用动。
:root { /* 基础层 */ --blue-500: #2F5BEA; --gray-100: #F7F7F8; --gray-300: #E5E5E5; --gray-700: #666666; --gray-900: #1A1A1A; --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-6: 24px; --space-8: 32px; --radius-sm: 4px; --radius-md: 8px; --radius-lg: 16px; /* 语义层 */ --color-brand: var(--blue-500); --color-text-primary: var(--gray-900); --color-text-secondary: var(--gray-700); --color-border: var(--gray-300); --color-bg-page: var(--gray-100); --padding-card: var(--space-4); --gap-section: var(--space-8); --radius-card: var(--radius-md); }设计工具那边也有对应的能力,变量、样式、组件属性都能承载这套东西。关键是命名要对齐,设计里叫color-text-secondary,代码里也叫这个,别一边叫"次要文字"一边叫text-gray-2,那等于白做。我见过团队因为命名不统一,对接时靠截图对照,效率低得离谱。
注意:Token 不要一上来就搞几百个。先覆盖最常用的二三十个,跑一段时间再补。一开始铺太大,没人记得住,最后大家还是回去写魔法数字。
4. 交互反馈与可用性:那些"看起来没问题"的坑
4.1 反馈的三种时间尺度
用户操作之后,界面必须给回应,这是最基本的体验要求。但"给回应"不是简单弹个提示,它要匹配操作的耗时。我按时间分成三档来看。
0 到 100 毫秒,用户感觉是"即时"的。这一档不需要 loading,但需要视觉状态变化,比如按钮按下的凹陷、颜色的加深、勾选框的打钩。这一档如果缺失,用户会怀疑自己点没点上,然后重复点击,造成重复提交。很多线上事故就是这么来的——按钮点下去没反馈,用户连点五下,订单创建了五个。
100 毫秒到 1 秒,用户能感觉到等待,但还能接受。这一档适合用轻量的局部反馈,比如按钮内出现 spinner、骨架屏开始显示。不要用全屏遮罩,那会打断用户的上下文。
1 秒以上,用户开始失去耐心。这一档必须给明确的进度信息,骨架屏、进度条、分步提示都行。超过 5 秒的操作,最好能预估时间或者支持后台执行 + 完成通知。
这个分档的价值在于,它让你在设计阶段就能判断"这里该用什么反馈",而不是凭感觉。我审稿时会特意问一句:这个按钮点下去,如果接口要 3 秒才返回,界面是什么样?很多稿子答不上来,因为只画了成功态。
4.2 表单、按钮、列表的高频问题
表单是 UI 问题最集中的地方,几乎没有之一。我列几个几乎每个项目都会遇到的:
标签位置。放左边还是上边?左边省纵向空间,但标签长度不一时会造成输入框起始位置参差,阅读动线是 Z 字形,效率低。放上边动线是直线,扫读更快,也更容易适配窄屏。我的建议是移动端一律放上边,桌面端在表单项少、标签短的情况下可以放左边,但必须右对齐,让输入框左边缘对齐。
必填标识。星号放标签前还是后?放前面更醒目但打断阅读,放后面更自然。真正的问题不是位置,而是如果没有必填项,就不要画星号;如果全是必填,也不该每个都标星,而是标注"选填"。这个逻辑很多人搞反。
校验时机。边输入边校验最容易惹人烦——用户刚敲第一个字就红一片。合理时机是失焦时校验,提交时整体校验,错误修正后立即清除提示。
主次按钮。一个弹窗里两个按钮,哪个是主?常见错误是两个都做成实心,用户分不清该点哪个。规范做法是一个实心主按钮 + 一个描边或文字次按钮,主按钮放在右侧(阅读终点),危险操作用语义色并加二次确认。
列表这边的高频问题是空状态和加载更多。空状态不能只写"暂无数据",得告诉用户为什么空、能做什么——"还没有收藏,去看看推荐"比"暂无数据"有用得多。加载更多则要考虑:到底了要不要提示?加载失败能不能重试?切换筛选条件后列表是重置还是保留滚动位置?这些都要在设计阶段明确。
4.3 可访问性基础:对比度、焦点、触控尺寸
可访问性听起来像 compliance 的事,其实它直接决定产品好不好用。三个最基础的指标值得记一下。
对比度。正文文字与背景的对比度建议不低于 4.5:1,大号文字(18px 以上或 14px 加粗)不低于 3:1。这个数值不是拍脑袋的,是为了让视力不佳或在强光下使用的人也能看清。浅灰色文字配白底是重灾区,很多设计稿里#CCCCCC的文字在白底上根本读不了。
焦点样式。键盘操作的可见焦点圈是刚需,但很多项目为了"好看"直接outline: none,结果键盘用户完全不知道自己在哪。正确做法是保留焦点但自定义样式,用品牌色描边或者加阴影,既好看又能用。
/* 错误做法 */ button:focus { outline: none; } /* 推荐做法 */ button:focus-visible { outline: 2px solid var(--color-brand); outline-offset: 2px; }触控尺寸。手指点击的可靠区域建议不小于 44×44px(iOS)或 48×48dp(Android)。视觉上图标可以只有 20px,但热区要撑大。很多移动端界面图标挨得太近,用户点 A 命中 B,这种问题在真机上特别明显,模拟器上完全看不出来。
注意:对比度检查不要只靠肉眼。设计工具里一般有插件能直接算,或者用在线工具输入两个色值就能出结果。肉眼在好屏幕上看着"还行"的颜色,到了廉价显示器或者户外光线下可能完全失效。
5. 适配与交付:从设计稿到能跑起来的界面
5.1 响应式断点与流式布局的取舍
做适配最容易走两个极端:要么只出一套移动稿,桌面端拉伸得惨不忍睹;要么每个断点单独画一套,维护成本爆炸。比较务实的思路是先定断点,再定布局策略。
断点不用定太多,我一般用四档:小于 768 是移动,768 到 1024 是平板/小笔记本,1024 到 1440 是常规桌面,1440 以上是大屏。这四档覆盖绝大多数场景。定了断点之后,每一档要明确两件事:内容的列数和容器的行为。
列数上,卡片列表在移动端一列、平板两列、桌面三到四列,这是常识。容器的行为更关键:是固定最大宽度居中(内容型页面常用),还是流式撑满(工具型页面常用)?我倾向于内容型页面设一个max-width: 1200px左右居中,两边留白,这样在大屏上阅读行宽不会太长;工具型页面(后台、编辑器)则应该撑满,把空间用起来。
还有一个容易忽略的点:断点之间怎么过渡。如果只在断点处设置样式,缩小窗口时会看到元素"跳变"。更平滑的做法是用clamp()做流式字号和间距,让值在区间内连续变化:
.title { font-size: clamp(20px, 2.2vw, 32px); }这样标题在窄屏到宽屏之间是渐变的,不会在某个宽度突然变大。
5.2 切图、标注、命名规范
交付环节的质量直接决定开发效率和还原度。切图这块,图标优先用矢量格式(SVG),位图只在照片、复杂插画时用。位图要出多倍图,至少 2x 和 3x,并且要控制体积——一张首屏大图超过 300KB 就值得压缩了。
命名规范是最容易被忽视的。我见过太多未命名-1 copy 3@2x.png这样的文件,开发拿到手完全不知道该放哪。好的命名应该包含模块、元素、状态、倍率,比如order-card_btn-primary_hover@2x.png。团队里定一个简单规则,所有人照着来,能省掉大量沟通。
标注这块,现在设计工具基本能自动出,但自动标注解决不了"意图"问题。开发看到margin: 16px,但不知道这 16 是"卡片内边距"还是"两个元素之间的间隔",改的时候容易改错地方。所以我建议在关键模块旁加一句说明,比如"此间距随屏幕拉伸,用 flex gap 实现"、"此高度固定,内容超出滚动"。花五分钟写注释,能省掉半小时的来回确认。
5.3 走查清单:上线前必看的检查项
设计稿还原度不是靠开发自觉,是靠走查。我整理了一份自己常用的清单,按优先级排:
| 优先级 | 检查项 | 常见问题 |
|---|---|---|
| 高 | 所有交互状态是否实现 | 只有默认态,悬停/禁用/加载缺失 |
| 高 | 空数据、加载失败是否有界面 | 白屏或只有一行默认文字 |
| 高 | 长文本是否溢出或截断 | 用户名超长撑破卡片 |
| 高 | 极端数据是否正常 | 数字上亿、列表只有一条 |
| 中 | 不同屏幕宽度下布局 | 窄屏元素重叠、宽屏内容过散 |
| 中 | 深浅色主题切换 | 硬编码颜色不受主题控制 |
| 中 | 图片加载前后的布局 | 图片加载完把内容顶下去 |
| 低 | 动效时长与缓动曲线 | 不同地方用了不同的时长 |
长文本和极端数据这两项特别容易被漏。测试时用的都是"张三""测试商品",一上线真实用户名二十个字,卡片直接歪掉。我的习惯是在开发环境里专门造几条脏数据,超长中文、纯英文无空格、超长数字、空字符串、emoji,跑一遍看会不会炸。
6. 常见问题与排查实录
6.1 高频问题速查表
做项目做得多了,会发现 UI 相关的问题高度集中在某几类。下面这张表是我这些年反复遇到的问题和处理办法,遇到类似现象可以先对着查。
| 现象 | 可能原因 | 处理思路 |
|---|---|---|
| 页面看起来"松散"没档次 | 间距无系统、字号阶梯混乱 | 统一到 8pt 栅格和字号阶梯 |
| 界面显得"灰扑扑" | 缺少对比、层级不够 | 强化主次,加深主文字、抬高留白 |
| 改一个颜色全站乱 | 颜色未 Token 化、硬编码 | 抽出语义变量,组件引变量 |
| 按钮点击无反馈 | 缺按下态或加载态 | 补状态,加防重复提交 |
| 表单报错布局跳动 | 错误提示未预留高度 | 固定提示区高度或绝对定位 |
| 长用户名撑破布局 | 缺省略或换行策略 | 单行 ellipsis,多行 line-clamp |
| 列表为空一片白 | 未设计空状态 | 补空状态文案和引导按钮 |
| 图片加载导致抖动 | 未预留尺寸 | 容器设宽高比占位 |
| 移动端点错按钮 | 热区不足 | 热区撑到 44px 以上 |
| 深色模式下看不清 | 颜色硬编码不受控 | 用变量,单独适配暗色值 |
6.2 几个我踩过的坑
说几个印象深的。第一个是图片占位。早期做商品列表时,图片没设固定比例,结果加载过程中卡片高度是 0,图片一出来整页往下推,用户正在点的位置跑了,误触率特别高。后来改成容器固定比例、图片绝对定位填充,问题就没了:
.thumb { position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-radius: var(--radius-md); background: var(--color-bg-page); } .thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }第二个是滚动穿透。弹窗打开后,背景页面还能滚,手指在弹窗上滑,底下页面跟着动,体验很糟。解决方式是打开弹窗时给 body 加overflow: hidden,同时记录滚动位置,关闭时恢复。这个方案在 iOS 上有历史问题,更稳的做法是对弹窗本身做滚动容器,并阻止滚动链传导:
.modal-body { overflow-y: auto; overscroll-behavior: contain; }第三个是字体加载导致的闪烁。自定义字体加载前用的是系统字体,加载完成后文字重排,看起来像是跳了一下。处理方式是用font-display: swap先显示系统字体,同时把字体做子集化,只保留用到的字符,体积能压到很小,加载几乎是瞬时的。
第四个是暗色模式的颜色。直接给暗色模式反相是不行的,纯白背景变纯黑,很多阴影和边框就消失了。正确做法是单独定一套暗色值:背景用深灰#121212而不是纯黑,卡片用略浅的#1E1E1E,靠亮度差而不是阴影来分层,文字不用纯白用#E8E8E8,品牌色适当提亮保证在深底上的对比度。
注意:暗色模式里不要用纯黑纯白。纯黑背景配纯白文字对比度过高,长时间看会累;而且 OLED 屏上纯黑和纯白切换时容易有拖影。稍微收一点,观感会好很多。
6.3 入门练手路径建议
最后说点实打实的练法。光看概念没用,得动手。我给新人安排的路径一般是这样的:
第一步,抄一个真实产品的一个页面,像素级还原,包括间距、字号、圆角、阴影、图标。目的不是抄,是通过抄去理解别人为什么这么排。抄完对比原图找差异,差异往往就在你没注意的细节里。第二步,把这一页的状态画全,把加载、空、错误、长文本、极端数据都补上。这一步最能训练系统思维。第三步,抽成组件,把重复的元素整理成可复用组件,试着改一个值看看是不是所有地方都跟着变。第四步,写出来,用 HTML/CSS 或者任意框架实现一遍,你会立刻发现哪些地方设计时想当然了。第五步,改一版,把实现时发现的问题回到设计稿修正,形成闭环。
这个循环跑上三四遍,你对 UI 的理解会有质的提升。因为你会发现,很多设计上的"我觉得挺好",到代码里是不成立的;很多实现上的"差不多得了",到用户那里就是体验事故。设计和技术之间的这条缝,只有亲自跨过去几次才知道有多宽。
我自己的经验是,最开始那半年别追求"设计感",先把规范和状态这两件事做扎实。规范让你做得快,状态让你做得稳。等这两样变成肌肉记忆了,再去研究风格、动效、品牌调性,那时候你做的每一稿才能真正落地,而不是停在图层里好看。