设计系统
适用场景
在需要前端设计的机械实现约束时使用本技能:Token 架构、字体层级、加载顺序、FOUT 防护、Chrome 稳定性、动效时序、颜色语义。在构建组件、页面或设计系统时与 design 配合使用。(美学方向由……决定)
实现 UI(组件、页面或设计系统)时与 design 配合使用。每一种颜色、字体、动效选择都应可追溯到这些规则。
Token 架构
所有颜色都映射到一组精简的基元值上。不允许随机的十六进制值。
- 前景色(Foreground):文本层级(主要、次要、弱化)。
- 背景色(Background):表面层级(基础、抬升、浮层)。
- 边框色(Border):分隔层级(细微、默认、强调)。
- 品牌色(Brand):身份与主要强调。
- 语义色(Semantic):危险、警告、成功(可选信息)。
在代码中使用 Token(CSS 变量、主题对象),UI 中绝不允许硬编码十六进制值。
字体排版
- 层级:标题——更粗的字重、更紧凑的字距,体现存在感。正文——舒适的字重,便于阅读。标签/UI——中等字重,在较小字号下仍清晰。数据——等宽字体,配合
tabular-nums保证对齐。 - 综合尺寸、字重、字距,让层级一目了然。如果眯起眼分不清标题与正文,说明层级太弱。
- 字体:展示字体与正文字体搭配;保持层级一目了然。字体选什么属于方向问题,不属于机械规则——从领域出发,避开最初/默认的本能(即均值);参见 design-spatial §2。
- 数据(仅功能性):真实对齐的数字、ID、时间戳使用等宽字体配合
tabular-nums——等宽只有在数值按列对齐时才真正发挥作用。不要在装饰性小标题/元信息微文本(例如 "35MM · DEVELOP · SCAN"、伪规格说明)上随手点缀等宽字体来营造"技术感"——那是当下流行的套路,而不是数据本身。参见 design-spatial §2。
加载顺序——先看见的先加载
首屏必须在快速完成绘制的同时保证完整、正确。每一项资源都要按用户是否最先看见来排序;其余的稍后排队。
- 只优先处理首屏资源(标题文本、主图/视频、品牌标识)。全部预加载等于什么都没预加载——真正关键的资源会在带宽竞争中落败。挑选首屏中真正会用到的少数几个,优先加载它们。
- 字体:自托管 WOFF2。 将 OTF/TTF 转为 WOFF2(Brotli;体积约为一半,字形一致),并通过
<link rel="preload" as="font" type="font/woff2" crossorigin>预加载首屏使用的字重。绝不要使用阻塞渲染的第三方字体样式表——Google Fonts 的<link>会增加一次 CSS 往返和额外的 DNS/TLS,字体还没开始下载;改为自托管。 - LCP 图片/视频: 在主图(或视频封面)上加
fetchpriority="high";当它通过 CSS 引用时使用<link rel="preload" as="image">(解析器无法提前看到 CSS 的url())。首屏位置绝不能是空的——提供一个封面/低分辨率占位,避免出现空白帧。 - 首屏以下: 图片使用
loading="lazy" decoding="async";视频使用preload="none"(或"metadata");非关键 JS 加defer。始终通过aspect-ratio或width+height预留空间,避免延迟加载的媒体引起布局偏移(CLS)。 - 保持阻塞渲染的 head 最小:内联关键 CSS,其余延迟加载。
绝不让字体突然出现(杜绝 FOUT)
font-display: swap 本身就是那个"突然出现"——它先画一个兜底字体,再替换为网络字体并触发重排。对任何会被用户看着加载的文本(标题、文字标志、主标语),都不要使用。规则是绝对的:标题/展示文本绝不能闪现兜底字体或发生重排。
- 以真实字体就绪作为可见性门控。 同步地在
<head>中为<html>添加fonts-pending类,把展示字体文本的opacity设为0。在document.fonts.ready时——通过document.fonts.load('<weight> 1em "Family"')为每个关键字体显式触发——切换到fonts-ready类并淡入文本(约 0.5 秒)。始终包含一个安全超时(约 2.5 秒),无论是否就绪都强制显示,避免字体加载失败导致文本永久不可见。 - 与上文 preload + WOFF2 配合使用,使隐藏窗口只有几百毫秒,而不是几秒——这种淡入看起来是有意为之,而不是卡顿。
- 对于正文中可以容忍极轻微替换的情况,至少要消除重排:通过带
size-adjust/ascent-override/descent-override的兜底@font-face(或font-family兜底列表)让兜底字体占据与网络字体相同的字形指标,使替换不产生位移。
实操示例——一个 AR 产品调研页面:head 中的脚本切换 fonts-pending → fonts-ready(标题在 fonts.ready 时淡入,2.5 秒兜底),预加载首屏四种字重的 WOFF2,自托管品牌字体以避免 Google 的额外往返。
慢加载内容——绝不要展示难看的中间态
任何可能需要稍长时间才能就绪的内容——字体(如上所述)、大图、视频、<canvas> 场景、Three.js / WebGL、懒加载的 React 岛块、任何通过网络获取或执行重的主线程初始化的内容——必须做到快速就绪或优雅加载。浏览器默认行为(空白框 → 部分绘制 → 重排 → 最终态)就是那个难看的中间态。必须拦截它。
两个杠杆;同时使用:
- 加速就绪。 压缩(字体用 WOFF2、glTF 用 Draco、图片用 WebP/AVIF、视频用 h264/h265 加
preload="metadata")。预加载首屏真正需要的少数资产(<link rel="preload">)。首屏以下的内容懒加载,避免 LCP 资源争夺带宽。通过aspect-ratio、width+height预留容器,避免延迟内容引发 CLS。 - 优雅加载。 用风格统一的占位元素隐藏加载中的状态,待真实内容就绪后再淡入。骨架屏、低分辨率模糊封面、单个 ASCII 字符,甚至是容器的背景色——任何与设计语言一致的内容都比默认的部分绘制更好。
"难看"具体长什么样,以及对应修复:
| 症状 | 修复 |
|---|---|
标注标签在 JS 定位前都堆在 translate(0,0)(容器左上角) |
标签初始 opacity: 0,配合 transition: opacity ~0.35s;首次投影时设置内联 opacity → 由 CSS 完成淡入。 |
| Canvas/WebGL 首帧渲染为空/黑色 | 在同一容器中先显示占位(CSS 图形、低分辨率封面图,或纸张/骨架填充);待真实首帧渲染完成后移除。 |
| 懒加载图片请求完成时直接出现并引发布局跳动 | aspect-ratio + <link rel="preload">(首屏)或 loading="lazy" decoding="async"(首屏以下);首屏渲染时从 opacity:0 在 load 事件上淡入。 |
| 视频封面在播放时突变为第一帧 | poster 与你能控制的静帧一致;playing 事件触发时,过渡已经干净完成。 |
| 3D 模型毫无过渡地"出现"在屏幕中央 | 让画布保持可见但 opacity: 0;在 GLTFLoader 成功回调中、首次 tick() 之后,切换 .viewer-ready 类(或直接设置内联 opacity)。 |
| 懒加载 React 岛闪现比"无 UI"还难看的兜底 | 将 Suspense 兜底替换为与最终布局一致的骨架,而不是 spinner。 |
经验法则:如果用户在加载途中截屏,你会觉得丢人,那就有责任为它设计一个优雅状态。占位不必花哨——它必须有意为之,尺寸正确,且与即将到来的内容使用相同的设计语言。
Chrome 保持稳定——状态文字不能引发布局变化
持久化的 Chrome(顶栏、导航、工具栏、搜索栏、状态区)必须保持恒定高度,无论其中出现什么文本。临时状态/加载/说明文案——"loading model…"、"N matching · M indexed"、空状态提示——不允许换到第二行把相邻控件挤下去。一个随消息长度伸缩的状态区是布局抖动 bug,不是动态内容。
- 约束为单行: 使用
white-space: nowrap; overflow: hidden; text-overflow: ellipsis,让最长消息截断而非换行。 - 提前预留空间: 给容器一个固定的
height(或min-height),按消息长度设定,使最短和最长状态——以及空状态——占据相同的空间。
只有内容区可以移动,Chrome 必须保持稳定;由临时文本引起的布局偏移读起来就像粗糙的故障。(这条规则避免的具体故障:在搜索应用中,模型加载消息换行成两行把搜索栏挤下去。)
动效
- 保持时序统一且有目的;一个精心编排的瞬间(带
animation-delay的交错加载)胜过零散的微交互。HTML 优先用纯 CSS;React 使用 Motion 库。(面向公开/多用户项目时尊重prefers-reduced-motion。) - 克制/专业 UI 的默认值(作为起点,不是定律):微交互约 150 毫秒,较长的过渡 200–250 毫秒,缓出。俏皮/玩具般的基调(design-thinking)可能需要 spring/bounce 和更长节拍——让动效手感匹配所选方向,而不是默认使用这些数字。
- 编排——对于超出单个微交互的内容(路由/页面过渡、列表重排、揭示、共享元素),加载 references/motion-choreography.md:何时一个过渡值得保留(必须传达信息,否则删掉)、实现哪些种类以及顺序、按导航类型选择风格(仅在层级/有序场景使用定向滑动——同级之间使用滑动是错误的层级暗示;横向切换用淡入)、时长表,以及工艺细节(仅限合成器属性、变形时的运动模糊、绝不对文本做栅格缩放、持久 Chrome 隔离)。框架无关。
滚动叙事(scrollytelling)
对于解释性/编辑性/数据讲解类内容,优先选择滚动驱动图形而不是点击交互式控件。读者默认会滚动;让他们为推进一段说明而点击切换会增加摩擦并被跳过。使用 NYT/Pudding 模式:固定一张图(position: sticky),让短文本"步骤"从它旁边滚过,由每个步骤驱动图形状态。
- 机制: 一个
IntersectionObserver,配合rootMargin: '-48% 0px -48% 0px'(threshold 0),让一个步骤恰好在穿过视口中线时变为"激活";激活索引重新渲染被固定的图形。约 30 行——这就是去依赖的 scrollama;不要引入滚动库。 - 布局: 两列——一列是滚动的步骤,另一列是
sticky top-0 h-screen的图形;移动端堆叠时图形在顶部固定。每一步约 85vh,使每屏恰好居中一个;非激活步骤卡片调暗(opacity:.3),让当前步骤突出。 - 图形是激活步骤的纯函数(
graphic(active)),自身不持有任何点击状态——因此它能确定性截图/导出,并可降级为静态图。在状态之间添加动画(颜色/宽度/透明度,300–700 毫秒),让滚动连续而不是跳跃。 - 不适用的场景: 仪表盘、工具、表单——任何用户操作而不是阅读的内容——保持可交互。Scrollytelling 用于叙事,由你掌控节奏。(面向公开/多用户构建:根据上面的动效说明尊重
prefers-reduced-motion;保留状态变化但去掉补间。)
空间构图与布局
栅格系统、8 点间距标尺、视觉权重平衡、对齐以及"渲染—批评"循环位于 design-spatial (../design-spatial/SKILL.md)——是本文件 Token/字体/颜色规则的机械对应物。在编排页面、仪表盘或组件时随时加载它。(属于本节的方向要点:构图野心要匹配愿景——极繁主义配得上繁复/分层的代码;极简/精致要求克制与精确的间距。)
嵌套圆角(仅当一个圆角元素嵌套在另一个圆角元素内时适用)
不鼓励把东西都变圆——本规则只处理圆角元素嵌套在另一个圆角元素中的情况(卡片里的按钮、容器中的内嵌面板)。嵌套时:
- 子元素圆角 ≤ 父元素圆角,绝不能更大(子元素比父元素更圆,会显得从里面鼓出来)。
- 同心圆为理想情况:
子圆角 = 父圆角 − 间隙(两者之间的内边距),让两条曲线平行,内圆角呼应外圆角。在圆角父容器内放平直/无圆角的子元素没问题;真正显得别扭的是不匹配、非同心的曲线。
颜色
- 调色板来自领域:颜色应当感觉来自产品所处的世界,而不是贴在表面。
- 不止冷暖:安静与喧闹、密集与舒展、严肃与俏皮、几何与有机——不仅仅是暖色/冷色。
- 颜色承载含义:灰色搭建结构;颜色传达状态、动作、强调、身份。毫无动机的颜色就是噪音。(克制——一个强调色,而不是五个——是方向性原则;参见 design-thinking → 把冲击力留给标点。)
- 对比度——APCA 决策,WCAG 守门。 对于感知性的对比度判断(这段文字在这个底色上是否舒适易读?),优先使用 APCA(apcacontrast.com)——它对明度感知的建模远胜于 WCAG 2 的比例,WCAG 在浅底深字和中调场景下经常误判。把 WCAG 2(4.5 / 3:1) 留作合规底线——它是
design-spatial中layout-audit.js的门禁,也是无障碍标准的硬要求。用 APCA 来设计,用 WCAG 来认证。 - 交互态增强对比度。
:hover、:active、:focus必须比静态态更突出——更高对比度,而不是更低。让 hover 反而降低对比度(例如把文字颜色往背景色方向调浅)读起来像是禁用态。
避免通用/趋势化外观(Inter、紫底白字、千篇一律的深色玻璃卡片),并在代际之间做出变化属于 design-spatial §2 的内容——不在此重复。
背景与细节
氛围胜过平涂——但要匹配所选的美学,而不是默认套路。条件反射式地使用渐变网格/噪点/颗粒来营造"高级感",本身就是设计师趋势的均值(design-spatial §2);只在方向真正需要时才使用,绝不为装饰而装饰。
局限
- 仅当任务明确匹配其上游来源和本地项目上下文时使用本技能。
- 在应用变更前,验证命令、生成代码、依赖、凭据以及外部服务行为。
- 不要把示例当作环境特定测试、安全审查或破坏性/高成本操作的用户批准的替代品。