完整配方与变量接口
前七页逐条拆过的声明合回一处,就是下面这份样式表。本页先看它作为一个整体的变量接口设计,再给出实测出的一处缺口。
1 · 配方全文
textarea {
--_rows: calc(var(--textarea-rows, 5) * 1lh);
--_max-rows: calc(var(--textarea-max-rows, 20) * 1lh);
--_foreground: var(--textarea-foreground, var(--color-lightest));
--_background: var(--textarea-background, var(--color-darkest));
--_border-color: var(--textarea-border-color, var(--color-dark));
display: block flow;
/* ① 把 box-sizing 还原成未指定的状态(content-box) */
box-sizing: unset;
/* ② 在父容器里铺满 */
inline-size: -webkit-fill-available;
inline-size: stretch;
/* ③ 最小高度取 --_rows 行 */
min-block-size: var(--_rows);
padding: 1em;
border: 1px solid var(--_border-color);
color: var(--_foreground);
background-color: var(--_background);
/* ④ iOS 的缩放对策:字号以 16px 为下限,更大时跟随 1rem */
font-size: max(16px, 1rem);
@supports (field-sizing: content) {
/* ⑤ 最大高度限制在 --_max-rows 行 */
max-block-size: var(--_max-rows);
/* ⑥ 关掉用户手动改变尺寸 */
resize: unset;
/* ⑦ 启用高度随内容变动 */
field-sizing: content;
}
}
原注释的 ③ 写的是「最大高度」而实际是 min-block-size,且称此后 rows 属性失效——两处都与实测不符,订正见 行数尺寸与 lh 单位 §2。上面已按实测改写。
--_rows 的像素落点与实测盒尺寸;末尾代码块输出当前变量下的计算值。2 · 两段式变量接口
五个私有变量都是同一个形状:
--_rows: calc(var(--textarea-rows, 5) * 1lh);
这个形状同时做了三件事。
其一,划出内部与外部的界线。--_ 前缀是社群约定的「私有」记号——CSS 没有可见性机制,调用方写 --_rows: 120px 照样生效,前缀能表达的只是一句约定:这一组不对外。对外的名字统一带 textarea- 前缀,避免与页面上其他组件的变量撞名。
其二,把单位留在组件内部。调用方传的是数字 5,不是长度 5lh。单位由组件决定,调用方不必知道内部用的是 lh 还是 em,组件日后换算法也不会破坏调用。
其三,给出默认值。var() 的第二个参数是 fallback,变量未定义时用它。配方的 fallback 又是一层 var(--color-lightest),指向项目的公共色板。
警示 · 最后这层嵌套有个前提:--color-lightest 之类必须真的存在。若项目里没有这个变量,整条链在计算值阶段失效,后果按属性是否继承而分叉——实测 background-color: var(--_background) 回到 initial value transparent,控件变透明;而
color 是继承属性,会继承父级的文字色。前者常常直到换了深色背景才被发现。稳妥的写法是给最内层也留一个字面量:var(--textarea-background, var(--color-darkest, #16181d))。
3 · 配方漏掉了字体族
font-size: max(16px, 1rem) 接管了字号,但配方没有任何一条声明碰 font-family。在一个没有 reset 的页面上把全文跑起来,实测结果是:字体族仍是 UA 的 monospace,line-height 仍是 normal。
这个遗留不止是观感问题,它会顺着 1lh 传到尺寸上。420px 容器、根字号 16px 下的实测读数:
| 项 | 实测值 | 本该是 |
|---|---|---|
font-family |
monospace | 页面正文字体 |
1lh |
19px(monospace 的 normal 行距) |
24px(line-height: 1.5) |
min-block-size |
95px | 120px |
| 边框盒 | 420 × 129px | 420 × 154px |
也就是说「至少五行」这句话是按 monospace 的行距兑现的,与页面正文的五行差出 25px。补法是加一行 font-family: inherit,或者更彻底的 font: inherit。
这处缺口还有一个不容易预料的后果:上表里的 129px 是不可移植的。给本系列写数字锁的 e2e 时,同一份配方在 playwright 自带的 Chromium 里量到的是 124px——那个构建里 generic monospace 的 normal 行距是 18px 而非 19px。1lh 的取值绑定在字体的度量上,而 generic family
解析到哪一款字体由平台决定。凡是没写死 line-height 的地方,跨浏览器、跨操作系统都不会得到同一个像素高度;本系列的测试因此对这类值只钉「五行加 34px」这条关系,不钉绝对数。
建议 · 用 font: inherit 时它必须写在 font-size: max(16px, 1rem) 之前。font 是简写,会把 font-size 一并重置为继承值,写在后面就把 16px 下限覆盖掉了。实测在一个 font-size: 13px 的父级里:font-size
在前、font: inherit 在后,控件字号 13px,缩放下限失守;顺序反过来则是 16px。
4 · 这份配方适用到哪里为止
七条声明解决的是一个控件的形态,且假定它独占一行、宽度铺满容器、高度随内容增长。表单里并排的两个短输入框、行内的可编辑标签、需要固定高度以保证页面不跳动的评论框,都不在这个假定里。
尤其要留意 inline-size: stretch 的作用域:它让 margin box 填满 containing block,前提是这个控件的宽度确实该由容器决定。放进 flex 或 grid 容器后,铺满本就由容器的对齐规则负责,这条显式尺寸会与 align-items /
justify-items 的效果叠加,两处规则谁说了算需要逐例确认,本系列没有覆盖这种情形。
配方给出的是一份作用于元素选择器的默认值,不是一个组件。它的收益在于把「新加一个 <textarea> 就要重写一遍尺寸」变成不必做的事;代价是它对全站每一个 <textarea> 生效,包括那些本不该铺满的。改哪一条、什么时候整条删掉,仍然是每个页面自己的判断。