u
This commit is contained in:
1 parent
b4498e5350
commit
bf9dca19de
47 files changed
+8592
-313
No files matched your search
@@ -112,6 +112,7 @@ export default defineConfig({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '快速上手', link: '/ui/usage/getting-started' },
|
||||
{ text: '文字排版与居中', link: '/ui/usage/typography' },
|
||||
{ text: '双向布局(RTL)', link: '/ui/usage/rtl' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
title: 文字排版与居中
|
||||
description: 组件库的字号、字重、行高与数字对齐约定,以及跨平台偏差的处理边界
|
||||
---
|
||||
|
||||
# 文字排版与居中
|
||||
|
||||
这一页回答两个问题:组件里的文字**该长什么样**,以及**怎么保证它在不同机器上位置一致**。
|
||||
|
||||
先说结论,它决定了后面每条规则的分寸:
|
||||
|
||||
> **用系统字体时,跨 OS 的「像素级一致」做不到,也不该追求。**
|
||||
> 只能保证「每台机器内部一致 + 偏差 ≤1px」,剩下的交给布局层兜底。
|
||||
|
||||
任何试图把按钮文字在 Windows / macOS / 国产 Linux 上对齐到同一个小数位的方案,
|
||||
最后都会变成一堆针对特定字体的补丁。**下面所有约定都只保证「稳健」,不承诺「完全相同」。**
|
||||
|
||||
## 字体:组件库不选型
|
||||
|
||||
`@g3soft/tokens` 只提供一个 **sans-serif 系兜底**,`--g3-font-family` 的真实取值
|
||||
由业务方在全局样式里覆盖:
|
||||
|
||||
```scss
|
||||
// 业务方(g3soft-erp 之类)自己维护,不在组件库里写
|
||||
:root {
|
||||
--g3-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei',
|
||||
'Noto Sans CJK SC', 'Source Han Sans SC', 'WenQuanYi Micro Hei', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**为什么组件库不自己定字体栈**:组件库不知道宿主跑在什么系统上、装了什么字体,
|
||||
也不该替业务方决定品牌观感。字体栈是一个**全局决策**,写在组件库里只会让
|
||||
每个消费方都要去覆盖它。
|
||||
|
||||
但有一件事必须提醒,它属于字体栈的责任而非常组件责任:
|
||||
|
||||
- **国产 Linux(麒麟 / UOS)务必带上 `Noto Sans CJK SC` 与 `WenQuanYi Micro Hei`。**
|
||||
这两款系统上没有 `Microsoft YaHei`、没有 `PingFang SC`,中文会落到 fontconfig 的
|
||||
兜底字体,出方块或发虚,**且每个发行版兜底结果不一致**。
|
||||
- 顺序是**西文在前、中文在后**,让数字和字母优先由西文字体渲染 ——
|
||||
这是中英混排最稳的顺序,反过来会让英文跟着中文字体走。
|
||||
- Win11 上 `Microsoft YaHei UI` 与 `Microsoft YaHei` 是**两款不同字体**,度量不同,
|
||||
先命中 UI 版更接近系统 UI 观感。
|
||||
|
||||
## 居中:靠 flex,不靠行高
|
||||
|
||||
组件里的文字居中,**由 `align-items: center` 保证,与 `line-height` 无关**。
|
||||
|
||||
原因:行盒中心相对基线恒为 `(ascent − descent) / 2`,这个值只由字体度量决定,
|
||||
`line-height` 设多少都不会改变它。`line-height` 只影响**行盒的高度**,不影响**文字在行盒里的位置**。
|
||||
|
||||
所以不要再用「`line-height` = 容器高度」去凑居中 —— 那是偏差的主要来源,
|
||||
而且那个技巧在字体度量变化时立刻失效。
|
||||
|
||||
`G3Button` 的实际写法:
|
||||
|
||||
```scss
|
||||
.g3-button {
|
||||
display: inline-flex;
|
||||
align-items: center; /* ← 居中在这一条 */
|
||||
justify-content: center;
|
||||
min-height: var(--g3-control-height, 32px);
|
||||
line-height: 1; /* ← 收紧行盒,不让行距顶开高度 */
|
||||
}
|
||||
```
|
||||
|
||||
`line-height` 取 **1** 而不是 `1.5`:按钮是定高容器,1.5 会让行盒比字形高出
|
||||
0.5em(14px 字号下约 7px),文案一换行就顶开按钮高度、把整排按钮顶得不齐。
|
||||
|
||||
### 图标容器的额外一层
|
||||
|
||||
按钮里的图标/文字容器**也必须各自居中**:
|
||||
|
||||
```scss
|
||||
.g3-button__icon,
|
||||
.g3-button__content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ 缺了这两条会造成「图标偏上」:`span` 默认 `display: inline`,内部 `svg` 会按
|
||||
`vertical-align: baseline` 对齐,于是 svg 坐在**文字基线**上而不是容器中心,
|
||||
实测比文字中心高约 1.5px。按钮自身的 `align-items: center` 只能居中 `span` 这一层,
|
||||
治不了 `span` **内部**的基线对齐。
|
||||
|
||||
## 数值列:等宽数字
|
||||
|
||||
金额、数量、单号、序号这类列要加 `font-variant-numeric: tabular-nums`。
|
||||
|
||||
比例数字(默认)里每个字形宽度不同,`1` 比 `8` 窄,于是每换一行小数点与个位
|
||||
就在水平方向抖一下,整列看着参差不齐。等宽数字把 0–9 钉成同一宽度,数字按位对齐。
|
||||
|
||||
`G3Table` 已经在组件内部处理好,**业务方不需要自己加**:
|
||||
|
||||
```scss
|
||||
.g3-table__inner td.text-r {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
```
|
||||
|
||||
判据是**右对齐**而不是「内容是数字」:库渲染 `td` 时读不到单元格的值,而本库约定里
|
||||
金额/数量/数值列一律用 `align: 'right'` 声明(见 `docs/examples/table/*.vue`)。
|
||||
|
||||
⚠️ 类名是 `text-r`,由 `stk-table-vue` 的 `getTDProps` 打上:
|
||||
|
||||
| `col.align` | 类名 |
|
||||
|---|---|
|
||||
| `'right'` | `text-r` |
|
||||
| `'center'` | `text-c` |
|
||||
| `'left'`(默认) | **不带类** |
|
||||
|
||||
左对齐的列没有任何类,所以 `td.text-r` 不会误伤普通文本列 —— 中文与拉丁文本来就
|
||||
不需要等宽数字,强行套上去反而会让正文字距松散。
|
||||
|
||||
## 表格边框:外框与格线的归属
|
||||
|
||||
`G3Table` 的外框(1px 描边 + 8px 圆角)由组件**自绘**在根节点上,格线仍走底座的
|
||||
`background-image` 渐变。这套组合下,有两条线容易画重或画丢,判据按下面的来。
|
||||
|
||||
**为什么格线不能改成真 `border`**:行内编辑器被钉死在 `var(--row-height)`(默认 30px),
|
||||
而 `td` 高度是按内容撑开的 —— 一旦 `td` 多出 1px 边线,内容盒只剩 29px 放不下 30px 的
|
||||
编辑器,整行被顶到 31px。行高是虚拟滚动 `translateY` 的计算基准,涨 1px 会一路累积成
|
||||
行错位。**背景渐变不参与盒模型**,所以格线只能走 `background-image`。
|
||||
|
||||
### 最右列竖线:按「是否贴住内右缘」判断
|
||||
|
||||
| 情形 | 处理 | 原因 |
|
||||
|---|---|---|
|
||||
| 右固定列(`.fixed-cell--right`) | 摘掉自身右线,交给外框 | 它贴住容器内右缘,自己的线会与外框叠成两条 |
|
||||
| 普通末列 | **保留**自身右线 | 列宽没铺满时它离内缘还有距离,摘了就「线凭空消失」 |
|
||||
|
||||
实测:右固定列的 `th`/`td` 右边缘都在内缘上(1242px)且自带 `270deg` 渐变;
|
||||
而列宽未铺满的表(容器内宽 1084px、列只到 800px)末列离内缘差 284px。
|
||||
|
||||
### 最后一行横线:按「行区是否铺满」判断
|
||||
|
||||
这是**唯一需要 JS 参与**的一处 —— 纯 CSS 表达不了「行区高度是否等于可用高度」。
|
||||
组件侧量出行区是否铺满,用 `.g3-table.is-body-fill` 把结论传进来:
|
||||
|
||||
| 行区状态 | 最后一行底边线 | 原因 |
|
||||
|---|---|---|
|
||||
| 铺满 | 交给外框 | 两者重合,否则叠成 2px |
|
||||
| 未铺满 | **该行自己画** | 外框内底边在它下面很远,摘了就缺线 |
|
||||
|
||||
⚠️ 判据**不能**是「是不是最后一行」(`tr:last-child`)。实测 180px 高的容器里只放
|
||||
1 行 30px 数据时,该行底边在 440px、外框内底边在 554px —— **差 114px 根本够不着**,
|
||||
此时摘线等于那唯一一条分隔线消失。这两个条件只有行区铺满时才等价。
|
||||
|
||||
### 空态
|
||||
|
||||
库的 `.stk-table-no-data.no-data-full` 会撑满剩余高度并**自带一条 `border-bottom`**,
|
||||
与外框的 `border-bottom` 落在同一条 y 上(实测都在 218px)→ 底部出现重叠线。
|
||||
组件已移除它自带的底边,**空态底边统一归外框**。
|
||||
|
||||
> 以上都由组件内部处理,业务方不需要写任何样式。若自定义了 `--g3-border-color`,
|
||||
> 外框与格线会一起跟随。
|
||||
|
||||
## 硬规定
|
||||
|
||||
| 项 | 取值 | 原因 |
|
||||
|---|---|---|
|
||||
| 字号 | `12 / 14 / 16`(偶数) | 避开 `13 / 15`:Windows 125% / 150% 缩放下奇数字号的亚像素误差会被放大 |
|
||||
| 控件高度 | `24 / 32 / 40`(8 的倍数) | 与栅格对齐,缩放后不易出现半像素 |
|
||||
| 数字列 | `tabular-nums` | 见上 |
|
||||
|
||||
字号与控件高度已由 `@g3soft/tokens` 提供:`--g3-font-size-sm/-/lg`、
|
||||
`--g3-control-height-sm/-/lg`。
|
||||
|
||||
## 关于字重的一个常见误解
|
||||
|
||||
网上常见「只用 400 和 700,避免 500/600 触发合成加粗」的说法。**这条在我们的
|
||||
目标平台上基本不成立**:`Segoe UI`、`Microsoft YaHei` 本身就带 500/600 字重,
|
||||
不会触发合成,字体度量也不会因此改变。
|
||||
|
||||
真正需要警惕的是**回退到只有 400/700 的字体**(某些 Linux CJK 字体)时才会合成加粗。
|
||||
那属于**字体栈问题**,归业务方管,不是组件该据此砍掉中间字重。
|
||||
|
||||
组件库因此保留 500/600 —— 它们承载了真实的层级语义(标题、选中态、合计行、
|
||||
搜索高亮等),降成 400 会丢失区分度。
|
||||
|
||||
## 关于 `text-box-trim`
|
||||
|
||||
`text-box-trim: trim-both` + `text-box-edge: ideographic` 是未来的正解,
|
||||
**但目前没有进组件库**,原因有二:
|
||||
|
||||
1. **值域不同步**:只测 `text-box-trim: trim-both` 就写 `text-box-edge: ideographic`,
|
||||
在部分实现里 `ideographic` 不被识别会**退化回 `alphabetic`** ——
|
||||
反而把中文按西文边缘裁,比不裁更偏。稳妥写法要两级嵌套 `@supports`。
|
||||
2. **引入新的不确定性**:组件库是发布给业务方的,`@supports` 里塞实验特性意味着
|
||||
**样式表现依赖消费方的浏览器版本**,同一个包在两台机器上长得不一样。
|
||||
这与本页开头的「跨 OS 一致做不到」是同一个逻辑。
|
||||
|
||||
## 自测
|
||||
|
||||
改动字体栈或行高后,**整行的基线与行高都会重算**,必须把所有小尺寸组件回归一遍
|
||||
(按钮三档尺寸、标签、分段控制器、分页器、表格行内编辑器)。
|
||||
|
||||
- 定性:playground 里逐个尺寸目视比对
|
||||
- 定量:`FontMetrics` API 可读浏览器实际采用的度量,但**它给不出「这行实际用了哪款字体」**
|
||||
—— 浏览器没有 API 能查回退结果,只能靠 `document.fonts.check()` 逐个试探,
|
||||
那是启发式而非实测。所以工具能定位偏差,但别信它的字体判定。
|
||||
- 设计稿差异:Figma 的行高框与浏览器渲染**不等价**,1px 内的差异属于正常,别去追绝对像素一致。
|
||||
|
||||
## 已知边界
|
||||
|
||||
- 跨 OS 像素级一致**做不到**,见页首结论。
|
||||
- 翻译成 `ar` 等语言时字体栈需另行调整(语言包目前只有 `zhCN` / `enUS`)。
|
||||
- 若业务方覆盖了 `--g3-font-family`,`G3Table` 内部硬编码的 `font-size` 不受影响,
|
||||
但行高与基线会变 —— 需重新回归。
|
||||
Reference in new issue
Block a user