
2 分钟#design #css
两套美工,不是两套皮肤
玻璃和纸不该共用一套变量。这一版把它们拆成两个对等的渲染程序:各写各的 token,各画各的层,切换只是换一个程序。
上一版里,纸是玻璃的「干态」:同一套 --glass-* 变量,[data-surface="paper"] 只是把数值改小、把模糊关掉。
能跑,但有个隐患——纸永远长在玻璃的骨架上。玻璃加一个新特性(比如跟着指针反光),纸就得跟着写一条「不要反光」的规则。久而久之,两套材质都变成了补丁。
这一版反过来了:它们是两个程序。
窗口只有骨架,材质是画上去的
<div class="window">
<span class="window__backdrop"></span> <!-- 材质层:折射 或 纸面 -->
<span class="window__sheen"></span> <!-- 反射层:只有玻璃有 -->
<div class="window__content">…</div> <!-- 内容 -->
</div>
骨架只负责几何:圆角、裁剪、过渡。材质层由 data-surface 选择用哪套程序来画:
| liquidglass | paper | |
|---|---|---|
| 材质层 | 壁纸副本 + SVG 位移场(真折射) | 暖白底色 + 纤维纹理 |
| 反射层 | 指针驱动的 specular 热点 | 没有 |
| 纸纹 | 水印(.05) |
本体(.07,暗色 .09) |
| 变量前缀 | --lg-* |
--pp-* |
两者没有任何共享的材质变量。--lg-* 在纸态下不存在,--pp-* 在玻璃态下也不存在——不存在,就没人会误用。
为什么非要拆开
除了「概念干净」,还有一个很实际的原因:性能预算不一样。
- 玻璃要算折射、要重绘、要 Worker,所以同屏最多两扇窗用真折射,其余退化成「色层 + 高光边」。
- 纸什么都不用算,只是贴一张静态纹理。
如果共用一套变量,这种差异只能靠一堆 if 条件去表达;拆成两个程序之后,它是结构上的差异——玻璃实例登记进渲染器,纸实例压根不登记。
按钮也是窗口
一条小规矩
按钮不是「长得像玻璃的按钮」,它就是一个窗口实例:只指定位置与大小,宽度由文字决定。
<a class="window window--btn" data-window data-mode="soft" href="…">
<span class="window__backdrop"></span>
<span class="window__sheen"></span>
<span class="window__content">读文章</span>
</a>
因为走的是同一套骨架与同一个材质程序,按钮的圆角、高光边、悬停位移和卡片永远一致——不是「对齐了」,是同源。
切换那一下
data-surface 一变,两个程序交替挂载:
- 玻璃的反射参数被清空(不再有人写
--sx/--sy); - 纸程序给每扇窗分配一点纤维偏移,避免整页纹理重复;
- CSS 的 240ms 过渡负责「水分蒸发」。
骨架一点没动,内容一点没动。这就是「切换成本低」的真正含义——换的不是样式,是渲染程序。
代价
说实话:代码量比一套变量多了大约三成,第一次读的人得同时看两段 CSS。
但换来的是一句可以一直用下去的话:
想改纸,只改纸;想改玻璃,只改玻璃。互不牵连。