# 康师傅看板 · 第八轮任务书(整体视觉升级 · 从「不俗」到「惊艳」) > 客户:「可以,但是不仅仅应该是调整玻璃这个效果吧,整体的 UI 视觉升级这只是一个小点吧」 > > **玻璃只是清单里的一小条。本轮是整体升级,六大板块,玻璃排最后。** > > 依据:进阶调研实测报告(Chrome 153 headless 实测,含 text-box / text-autospace / > grid-template-rows / scroll-driven animation / mask-composite 在真实浏览器里的验证结果)。 --- ## 0. 核心判断(先理解这个,再动手) > **「不俗」靠的是「没有一样东西做错」;「惊艳」靠的是「有一样东西做得比别人深」。** > > 上一轮 42 条数值全部正确 —— 这正是问题:**正确 = 无特征**。 > 做得越像 Vercel/Linear,越像模板。要学他们的**约束方法**,不是他们的**视觉皮肤**。 **惊艳感的三个真正来源(按性价比排序)**: 1. **中文排版真正做到位**(90% 中文 dashboard 没做,做了立刻拉开差距) 2. **用微图表替代趋势描述文字**(同时解决密度低和文字多) 3. **动效只服务于「变化」**,不服务于「出现」 --- ## 板块一:中文排版(最高优先级,差异点) 这一块是 90% 中文看板都没做的,做到了立刻不一样。**全部有实测数据支撑。** ### 1.1 行盒修剪 text-box(收益最大,3 行 CSS) **问题**:中文字体自带半行距,`padding:16px` 得到的视觉留白实际是 16px + 字体半行距(中文约 0.4em), **永远对不准 4pt 栅格** —— 这才是「看着规矩但就是不齐」的真因。 **实测**:40px 中文文本行盒高度 **55px → 30px(-45%)**。 ```css /* 所有标题、KPI 数字、单行标签都加 */ .stat-value, h1, h2, h3, .card-title, .metric { text-box: trim-both cap alphabetic; line-height: 1; } ``` **落地范围**:所有「单行文本容器」——大数字、标题、标签、按钮文字。 **不要**加在多行正文上(会破坏行距)。 ### 1.2 数字与单位分层(3 行 CSS,效果立竿见影) ```css /* 单位:小一号、灰一档、留 0.12em */ .u { margin-left: .12em; font-size: .92em; color: var(--text-2); } ``` 数据层插入:`text.replace(/(\d+(?:\.\d+)?)\s*(%|万|元|件|天|份|人)/g, '$1$2')` **效果**:`82份` → `82` 大而黑 + `份` 小而灰,数字立刻成为视觉主角。 ### 1.3 中英混排间距(实测 text-autospace 无效,必须数据层处理) **实测结论**:Chrome 153 对「S5场景与卖点设计TOP3」应用 `text-autospace` 后, 渲染宽度 **436.33px,与不加完全一致**,`getComputedStyle` 仍返回 `no-autospace`。 MDN 标 Baseline 2025,但 W3C 明确各浏览器行为不一致、WebKit 只单侧加距。 **可靠做法**:数据层插 U+2009 THIN SPACE(约 1/5 em)。 ```js function spaceCJK(s) { return s .replace(/([\u4e00-\u9fa5])([A-Za-z0-9])/g, '$1\u2009$2') .replace(/([A-Za-z0-9])([\u4e00-\u9fa5])/g, '$1\u2009$2'); } ``` **禁止**:用手插全角空格或普通空格冒充中英间距(1em 太宽)。 ### 1.4 字体栈(拉丁优先、CJK 兜底) ```css --font-sans: "Inter var", "Inter", "PingFang SC", "Noto Sans SC", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; --font-num: "Inter var", "SF Pro Display", -apple-system, "Helvetica Neue", sans-serif; ``` **为什么不能 system-ui 打头**:Windows 上拉丁字母会走雅黑的西文字形,字重和度量都不理想。 **顺序是设计决策**:Inter 在前接管拉丁字母与数字,中文只在其缺字时回落。 **注意**:不要引入几 MB 的中文字体包。Inter 只加载 latin subset,中文走系统字体。 ### 1.5 中文行高与字距(和西文规则不同) ``` 正文 line-height: 1.75(W3C CLReq:中文行间隙为字符框 50%-100%,75% 取中位) 行宽上限:48em(约 48 字);卡片内描述 32-36em 正文 letter-spacing: 0 ← 中文是满格方块,加字距反而散 12-13px 全中文小标题:+0.02em ← 只为提清晰度 ≥28px 大标题:-0.01em ~ -0.02em ``` **中文负字距上限 -0.03em**,超过笔画会粘连(西文 h1 可用 -4.75%,中文不行)。 ### 1.6 基线对齐补偿 ```css .icon-label > svg { transform: translateY(-0.5px); } /* 中文视觉重心比拉丁低 */ .num-inline { position: relative; top: -0.02em; } /* 数字与汉字 baseline 混排 */ ``` --- ## 板块二:信息可视化替代文字(解决密度低 + 文字多) ### 2.1 Sparkline 替代趋势描述句(纯 CSS,20 行) **核心规则(Tufte)**:sparkline 只表达**形状**,不替代**数字**。 > 「A sparkline shows shape, not values: there is no axis, and the dot marks the latest point. > **Print the number it summarizes beside it.**」 ``` ❌ 环比上月增长 12.4%,呈上升趋势 ✅ 12.4% ↗ [sparkline] ← 删掉 12 个汉字 ``` ```html
``` ```css .spark { display:flex; align-items:flex-end; gap:2px; height:20px; width:64px; } .spark i { flex:1; min-width:2px; border-radius:1px; height:calc(var(--v) * 100%); background:currentColor; opacity:.35; /* 历史柱:低不透明度 */ } .spark i[data-now] { opacity:1; /* 最新一根:实色,形成"落点" */ border-radius:1px 1px 0 0; } ``` **尺寸规范**:行内 1em 高 / 表格单元格 16px 高 60-80px 宽 / KPI 卡内 20-24px 高。 ### 2.2 进度环(SVG,别用 conic-gradient 做动画) ```html ``` ```css .ring { transform: rotate(-90deg); } /* 起点拨到 12 点 */ .ring-track { fill:none; stroke:rgba(0,0,0,.06); stroke-width:3; } .ring-fill { fill:none; stroke:var(--accent); stroke-width:3; stroke-linecap:round; stroke-dasharray:100; /* r=15.9 → 周长≈99.9 */ stroke-dashoffset:calc(100 - var(--pct)); transition: stroke-dashoffset 600ms cubic-bezier(.4,0,.2,1); } ``` **尺寸**:36px 卡内嵌 / 44px 列表行 / 56px+ 主 KPI。**低于 32px 别用环**(看不出弧长差),改用小横条。 ### 2.3 条形对比(替代三行百分比文字) ```css .bars .row { display:grid; grid-template-columns:64px 1fr 40px; align-items:center; gap:8px; font-size:12px; line-height:1; } .bars .track { height:4px; border-radius:2px; background:rgba(0,0,0,.06); overflow:hidden; } .bars .fill { height:100%; border-radius:2px; background:currentColor; width:calc(var(--v)*1%); transition:width 500ms cubic-bezier(.4,0,.2,1); } ``` **数值**:track 高 **4px**(3px 看不清圆角,6px 显笨)、圆角=高度/2、行距 6px、标签列 64px。 ### 2.4 热力小格 ```css .heat { display:grid; grid-auto-flow:column; grid-template-rows:repeat(7,10px); gap:2px; } .heat i { width:10px; height:10px; border-radius:2px; background: color-mix(in oklab, var(--accent) calc(var(--v)*100%), var(--surface-2)); } ``` **用 `color-mix(in oklab, ...)`** 而不是手写 5 个色阶 —— 保证任意强调色都生成视觉等距的 5 档。 格子 10px + gap 2px 是 GitHub contribution graph 的成熟尺寸。 ### 2.5 「标签 ···· 值」同行对齐(省一半高度) ```css .kv { display:flex; align-items:baseline; gap:8px; font-size:13px; } .kv::before { content:""; order:1; flex:1; border-bottom:1px dotted color-mix(in srgb, currentColor 20%, transparent); transform:translateY(-3px); } .kv .k { order:0; color:var(--text-2); } .kv .v { order:2; font-variant-numeric:tabular-nums; color:var(--text-1); } ``` 比「标签\n值」两行省一半高度。 --- ## 板块三:间距层级与密度(解决「节奏不匀」) ### 3.1 跨级间距规则(可校验,不是感觉) ``` 卡片内 padding = 16px (--space-lg) 卡片内元素之间 gap = 12px (--space-md) 卡片与卡片之间 = 24px (--space-xl) ← 1.5× 区块(section)之间 = 48px (--space-xl×2) ← 3× 页面顶 → 首个区块 = 48px(不能更大) ``` **上一轮的缺陷 1 根因**:告警框与下方区块**同属区块级,必须同为 24px**。 不是数值错了,是**跨级关系没区分**。 ### 3.2 密度分级 token(Sitka 标准) ```css :root { --density-space-xs: 4px; --density-space-sm: 8px; --density-space-md: 12px; --density-space-lg: 16px; --density-space-xl: 24px; } [data-density="compact"] { --density-space-xs: 2px; --density-space-sm: 6px; --density-space-md: 8px; --density-space-lg: 12px; --density-space-xl: 16px; } .card { padding: var(--density-space-lg); } ``` 一个 `data-density` 属性重写 8 个 token,全站自动跟随。 ### 3.3 密度平衡的真实做法 **不是「间距小」,是「同类项贴着、异类项留白」。** Linear 的 issue 列表每行内元素间距 4-8px(极密),但组与组之间 24px+。 **检验方法**:把页面缩成 25% 缩略图看。 - 所有留白宽度都一样 → **均匀呼吸 → 模板感** - 能看到明确的「块」在呼吸 → 对了 ### 3.4 一屏放更多信息的三个手段 1. KPI 从「2 列 × 4 行」改「4 列 × 2 行」,纵向高度砍半 2. `content-visibility: auto`(Chrome 153 实测支持),折叠区默认不渲染 3. 标签与值同行(见 2.5) --- ## 板块四:一致性系统(把 8 个阴影收到 3 档) **上一轮实测:阴影有 8 个变体,要求 3 档。这是工程问题,不是审美问题。** ### 4.1 三层 token 架构 ```css /* Tier 1 Primitive(组件禁止直接引用) */ :root { --sh-1: 0 1px 2px rgba(16,24,40,.04); --sh-2: 0 2px 8px -2px rgba(16,24,40,.06); --sh-3: 0 12px 32px -8px rgba(16,24,40,.12); --ring-1: 0 0 0 1px rgba(16,24,40,.06); } /* Tier 2 Semantic(只有 3 档,强制收敛的唯一手段) */ :root { --elev-1: var(--sh-1), var(--ring-1); /* 静置 */ --elev-2: var(--sh-2), var(--ring-1); /* 悬停 */ --elev-3: var(--sh-3), 0 0 0 1px rgba(16,24,40,.08); /* 浮层 */ } /* Tier 3 Component(组件只认语义名) */ .card { box-shadow: var(--elev-1); } .card:hover { box-shadow: var(--elev-2); } .popover { box-shadow: var(--elev-3); } ``` ### 4.2 强制收敛(不靠自觉) **grep 拦截**: ```bash grep -rn "box-shadow" src/ | grep -v "elev-\|--sh-\|--elev-" && echo "❌ 有脱离 token 的阴影" ``` **运行时断言**(加进 `scripts/check.mjs` 或验收脚本): ```js const seen = new Set(); document.querySelectorAll('*').forEach(el => { const v = getComputedStyle(el).boxShadow; if (v && v !== 'none') seen.add(v); }); console.assert(seen.size <= 3, `阴影变体 ${seen.size} 个,超过 3 档`, [...seen]); ``` ### 4.3 阴影即描边(Vercel 核心技巧) 把 1px 边框做成 shadow 的一层,而不是 `border`: ```css --ds-shadow-border-base: 0 0 0 1px #00000014; ``` **好处**:`border` 参与盒模型(hover 改宽度会抖动布局),`box-shadow` 不参与 —— **hover 加描边不会让卡片内容位移 1px**。 ### 4.4 焦点环(双环) ```css :focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg-1), /* 内环:和卡片同色,制造空隙 */ 0 0 0 4px var(--accent); /* 外环:可见指示 */ } ``` --- ## 板块五:动效只服务于「变化」 ### 5.1 数字滚动(只滚变化的位) ``` ❌ 页面加载时 0 → 1284 滚 2 秒 (AI 味 / 模板感) ✅ 数据刷新时 1284 → 1301,只有十位和个位滚 (高级) ✅ 首屏不动,等真实数据到达后再滚一次 (可信) ``` ```css .num { display:inline-block; font-variant-numeric: tabular-nums; /* 必须:保证位宽不跳 */ height:1em; overflow:hidden; line-height:1; mask-image: linear-gradient(#000 70%, #0000); /* 上下淡出,模拟机械感 */ } .num > .slot { display:block; transition: transform 500ms cubic-bezier(.4,0,.2,1); transform: translateY(calc(var(--i,0) * -1em)); } ``` **参数**:500-750ms、`cubic-bezier(.4,0,.2,1)`,**绝不用 `ease-in`**(会显得拖沓)。 ### 5.2 颜色过渡分通道 ```css .card { transition: box-shadow 180ms cubic-bezier(.4,0,.2,1), background-color 180ms cubic-bezier(.4,0,.2,1), color 240ms cubic-bezier(.4,0,.2,1), /* 文字慢 60ms,避免"字在闪" */ transform 150ms cubic-bezier(.4,0,.2,1); } ``` 文字颜色若与背景同步,会视觉上抢跑,显得廉价。 ### 5.3 光斑跟随鼠标(只亮边框,不是整卡发光) ```css .card { position:relative; isolation:isolate; } .card::before { content:""; position:absolute; inset:0; padding:1px; border-radius:inherit; background: radial-gradient(180px circle at var(--x,50%) var(--y,50%), color-mix(in srgb, var(--accent) 60%, transparent), transparent 60%); -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; opacity:0; transition: opacity 240ms cubic-bezier(.4,0,.2,1); pointer-events:none; } .card:hover::before { opacity:1; } ``` ```js card.addEventListener('pointermove', (e) => { const r = card.getBoundingClientRect(); card.style.setProperty('--x', (e.clientX - r.left) + 'px'); card.style.setProperty('--y', (e.clientY - r.top) + 'px'); }); ``` **半径 180px、不透明度 60%、衰减点 60%** —— 三个数一起决定「光斑够近才亮」。 **整卡发光 = 廉价**。 ### 5.4 Hover 内容揭示(progressive disclosure) 把卡片底部密集的决策信息默认收起,hover 展开: ```css .card .detail { display:grid; grid-template-rows:0fr; opacity:0; transition: grid-template-rows 220ms cubic-bezier(.4,0,.2,1), opacity 180ms; } .card .detail > div { overflow:hidden; min-height:0; } .card:hover .detail, .card:focus-within .detail { grid-template-rows:1fr; opacity:1; } ``` **`0fr → 1fr` 是唯一正确的 auto-height 动画方案。** **禁止 `max-height:999px`** —— 模板感重灾区,速度失真。 ### 5.5 滚动视差(零 JS,跑合成器线程) ```css @supports (animation-timeline: view()) { .reveal { animation: rise linear both; animation-timeline: view(); animation-range: entry 10% cover 30%; } @keyframes rise { from{opacity:0; transform:translateY(12px);} to{opacity:1; transform:none;} } } @media (prefers-reduced-motion: reduce) { .reveal { animation:none; opacity:1; } } ``` **偏移 12px,不是 40px。** 超过 24px 就开始像 AOS 模板库。 ### 5.6 长标签不截断 ```css .label { word-break: keep-all; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; } ``` flex 子项补 `min-width: 0`。 「S5 场景与卖点设计」19 字在 260px 卡内**折成两行**而不是截断(2 行 = 42px)。 --- ## 板块六:玻璃(最小的一块,不是重点) **Vercel 设计团队的 hard reject 清单明确包含 glass effects**,理由是: 全页玻璃 = 性能灾难 + 内容糊。 **折中方案(客户已批准)**: - **浮层保留玻璃**:侧栏、顶栏、底部 Tab、抽屉、筛选面板、弹层 —— 这些本来就是浮层 - **内容卡片减薄**:改成更实的白底(`rgba(255,255,255,.72)`)+ 1px 发丝描边 + 内高光,只留极轻微透明 ```css /* 浮层(保留玻璃) */ .glass-surface { background: rgba(255,255,255,.62); backdrop-filter: blur(28px) saturate(180%); } /* 内容卡(减薄,靠描边和内高光做质感,不靠糊) */ .glass-card { background: rgba(255,255,255,.72); backdrop-filter: blur(8px); /* 轻微 */ box-shadow: inset 0 1px 0 rgba(255,255,255,.9), var(--elev-1); } ``` --- ## 板块七:反例清单(15 条,全部禁止) Vercel 设计团队原话:**Hard reject decorative gradients, gradient text, glows, blobs, stripes, textures, grid backgrounds, glass effects, colored side rails, ornamental shadows, and fake depth.** 1. 渐变文字标题(`background-clip:text` + 紫蓝渐变) 2. 彩色左边框告警(`border-left:4px solid red`)→ 改 6% 染色背景 + 1px 同色半透明描边 3. 所有卡片都 `backdrop-filter: blur()`(本轮处理) 4. 浅色主题下 glow(`box-shadow: 0 0 40px accent`) 5. `100vh` 满屏英雄区 + 大字口号 6. **均匀呼吸**(所有间距都是同一个 16px 或 24px)← 模板感核心成因 7. `transform: scale(1.05)` hover → 挤压邻元素 8. **emoji 当图标**(📊 🚀 💡)← 最致命的廉价信号 9. `max-height: 999px` 展开动画 10. 每个数字从 0 滚到目标值(countUp) 11. 超过 3 档阴影 12. 纯 `#000` / `#FFF` → 用近中性(`#FAFAFA / #F2F2F2 / #EBEBEB / #171717`) 13. 圆角无嵌套关系 → 子 ≤ 父且同心 14. **`transition: all`** ← Vercel 明令禁止 15. AOS 风格每屏 40px 上移 + 依次出现 **逐条自查并报告:现有代码命中哪几条。** --- ## 板块八:空态与错误态 ### 8.1 空态(最能体现「被认真做过」) 三要素: 1. **说清为什么空**(「本月暂无新品提案」而非「暂无数据」) 2. **给一个动作**(按钮) 3. **保留骨架** —— 空态的卡片框仍在,只把内容换提示。**卡片消失 = 布局跳动 = 廉价** ```css .empty { display:grid; place-items:center; gap:8px; min-height:120px; /* 与有数据时近似等高 */ color:var(--text-3); font-size:13px; border:1px dashed color-mix(in srgb, currentColor 24%, transparent); border-radius:10px; } ``` ### 8.2 错误态(不要红色大块) ```css .alert { border-left: none; background: color-mix(in srgb, #E5484D 6%, var(--surface-1)); /* 6% 染色,不是 12% */ box-shadow: var(--elev-1), 0 0 0 1px color-mix(in srgb, #E5484D 16%, transparent); border-radius:10px; padding:12px 14px; font-size:13px; line-height:1.6; } ``` **6% 染色是关键数值**。13%(常见默认)会让整个区块变粉,立刻廉价。 --- ## 铁律 1. 不改路由、不改数据层 schema、不改组件 API 2. **字数不许回升**(类型页 ≤300 中文字、工作台 ≤350、报告中心 ≤200)—— 本轮要**进一步下降** 3. **对比度 ≥4.5:1**,逐项报实测 4. 手机 393px **不能退步**:底部 Tab、单列、无横向滚动条必须保持 5. 结构不动:卡片式、6 维筛选、来源四要素全保留 6. **相伴所有新效果要能一键关闭**(`[data-density]` / CSS 变量),便于回退 --- ## 验收(全部实测,报具体数值) ### 中文排版(5 项) 1. `text-box` 落地范围的元素数量 + 一个 40px 标题的实测行盒高度(应约 30px,原 55px) 2. 单位分层:`82份` 中 `份` 的 computed font-size 与 color 3. 中英混排:抽查 5 处含中英混排的文本,报是否插入了 U+2009 4. 字体栈:computed font-family 完整值 5. 正文 line-height(应 1.75)、大标题 letter-spacing(应 -0.01~-0.02em) ### 信息可视化(4 项) 6. sparkline 实际落地处数 + 删掉的趋势描述文字字数 7. 进度环/条形/热力的落地处数,报尺寸数值 8. 「标签····值」同行落地的处数 9. 一屏可见信息条数(对比上一轮的数值) ### 间距一致性(3 项) 10. 卡片内 padding / 卡内 gap / 卡间 / 区块间的 computed 值(应为 16/12/24/48) 11. 全页最大连续空白 12. `data-density` 切换是否生效(切 compact 报卡片 padding) ### 一致性系统(3 项) 13. **阴影去重后变体数(必须 ≤3)** + 报出这 3 个值 14. 卡片是否改用 shadow 描边(hover 时内容是否位移) 15. 焦点环 computed box-shadow ### 动效(3 项) 16. 数字滚动:是否只在值变化时触发,时长与缓动值 17. hover 展开:`grid-template-rows` 是否 `0fr→1fr`,时长 18. 全站 `transition: all` 剩余数量(必须 0) ### 反例自查(1 项) 19. **逐条报 15 条反例命中情况**(命中几条、分别在哪) ### 回归(3 项) 20. 手机 393px:Tab 栏、单列、`scrollWidth === clientWidth` 21. `node --test` 46/46 + `node scripts/check.mjs` 全绿 22. 字数:类型页/工作台/报告中心 --- ## 截图 `docs/screenshots/v11/`: `01-桌面-工作台` / `02-中文排版对比` / `03-sparkline落地` / `04-数字滚动` / `05-hover展开` / `06-光斑边框` / `07-空态` / `08-错误态` / `09-手机-工作台` / `10-手机-类型页` / `11-修前修后对比` --- ## 优先级 **P0(差异点,做了立刻不一样)** 1. 中文排版六条(text-box + 单位分层 + 中英间隔 + 字体栈 + 行高字距 + 基线补偿) 2. 阴影 8 → 3 档 + 运行时断言 3. sparkline 替代趋势文字 **P1(手感)** 4. 间距跨级规则 + data-density 5. hover 揭示 + 长标签不截断 6. 数字滚动(只滚变化位)+ 颜色过渡分通道 **P2(打磨)** 7. 光斑边框、滚动视差 8. 空态、错误态 9. 玻璃减薄(浮层保留、内容卡减薄) **先做 P0。中文排版是这一轮的核心价值,其他都是配菜。**