uni-app 主题与字号适配:用 CSS 变量少改一点代码
主题切换、深色模式和字号缩放如果分散写在每个页面里,后期会很难维护。把设计变量收在一处,是成本最低的整理方式。
跨端项目通常要面对多套主题、深色模式和不同字号。最直接的做法是在每个页面里写条件样式,但页面一多, 颜色和间距就会失控。我的做法是先定义一组 CSS 变量,再让组件只使用变量。
一、先定义设计变量
变量名按用途而不是颜色命名。比如用 --g-primary、--g-text、
--g-surface,而不是 --blue-500。这样换主题时只改变量值,不用改组件。
:root {
--g-primary: #1f6f5c;
--g-text: #33433b;
--g-text-soft: #7e8f86;
--g-surface: #ffffff;
--g-line: #dfe5df;
--g-font-scale: 1;
}
.theme-dark {
--g-primary: #6fd0b2;
--g-text: #dbe9e2;
--g-text-soft: #9bafa5;
--g-surface: #16211c;
--g-line: #2b3a33;
}
页面根节点根据主题状态挂上不同的类名,组件里只写 var(--g-primary)。
这样新增主题时,不需要搜索每个页面。
二、字号缩放要同时考虑行高和控件
只放大字体、不处理行高和按钮高度,容易出现文字贴边或按钮被撑破。我的做法是用一个缩放系数同时乘字号、 行高和固定高度。
.title {
font-size: calc(20px * var(--g-font-scale, 1));
line-height: 1.4;
}
.button {
min-height: calc(48px * var(--g-font-scale, 1));
padding: 0 calc(18px * var(--g-font-scale, 1));
font-size: calc(16px * var(--g-font-scale, 1));
}
行高用无单位数值更稳,比如 1.6,它会跟着字号一起缩放。
图标和固定尺寸的装饰元素可以单独处理,不必全部跟着放大。
三、注意 flex 子项被挤压
这是我在做勾选行时踩过的坑:左侧是一个固定大小的圆形勾选框,右侧是说明文字。 文字变长后,勾选框作为 flex 子项被压扁,圆形变成了椭圆。解决方法是禁止它收缩,并给文字留出可换行的空间。
.row {
display: flex;
align-items: flex-start;
}
.check {
flex: 0 0 18px;
width: 18px;
height: 18px;
box-sizing: border-box;
}
.text {
flex: 1;
min-width: 0;
line-height: 1.6;
}
min-width: 0 很关键。flex 子项默认的最小宽度是内容宽度,长文本可能不肯换行,
最后只能去挤其他元素。显式允许收缩后,文本才会正常折行。
四、把变量注入放在根节点
uni-app 页面可以通过根节点的 :style 绑定主题变量。这样切换主题时只更新根节点,
子组件通过 CSS 变量继承,不需要层层传 props。
<view class="page" :style="themeStyle">
<slot />
</view>
computed: {
themeStyle() {
return {
'--g-primary': this.theme.primary,
'--g-font-scale': String(this.fontScale)
};
}
}
五、保持组件边界清晰
主题变量适合放颜色、字体、圆角和间距这类全局设计决策;组件内部的具体布局仍然写在组件自己的样式里。 不要把业务状态塞进 CSS 变量,也不要用变量替代正常的组件 props。
做好这一层之后,新增一套主题通常只需要补一组变量值。页面代码基本不动,回归测试的范围也小很多。