跨端应用 约 7 分钟阅读

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。

做好这一层之后,新增一套主题通常只需要补一组变量值。页面代码基本不动,回归测试的范围也小很多。

← 返回文章列表 个人软件技术分享