☰
uni.scss全局样式变量实战:自动注入、变量规范与多端主题切换
2026/10/17 18:54:45 网站建设 项目流程

1. uni.scss解决的痛点:一套全局样式变量,省掉所有页面里的@import

做uni-app开发一段时间后,大家大概率都会遇到同一个让我很头疼的局面:项目里有几十个页面,每个页面的style里都飘着一行@import "../../styles/variables.scss";,路径深浅不一,有的页面忘了引导致颜色对不上,有的页面引了两份,还有的同事直接把颜色值写死在style里,改起主题色来像在做考古挖掘。

uni.scss就是为了收拾这种局面而存在的。它是uni-app项目里的一个特殊样式文件,放在项目的src目录下(HBuilderX创建的项目里通常在根目录)。这个文件不需要你在组件里手动import,编译阶段构建工具会自动把文件内容注入到每个vue组件的style块中。也就是说,只要你在uni.scss里定义了$primary-color: #2979ff;,全项目的任何组件style里都能直接用这个变量,不存在“忘记引入”这种问题。

更省心的是,uni-app官方在uni.scss里已经内置了一批常见样式变量,包括$uni-color-primary、$uni-color-success、$uni-color-warning、$uni-color-error、$uni-text-color、$uni-bg-color、$uni-font-size-sm、$uni-font-size-base、$uni-font-size-lg等等。如果你不覆盖它们,你甚至可以直接在组件里用这些变量名。多数实际项目会选择在uni.scss里重新声明同名变量做覆盖,把默认品牌色替换成自己的主色调。

我个人的感受是:uni.scss就是整个项目的“样式总闸”,它适合放一切需要跨页面复用的样式token,比如品牌色、功能色、字体大小梯度、间距梯度、圆角尺寸、阴影层级、z-index层级。凡是那种“可能在五个页面里出现同一个颜色值”的情况,都该进uni.scss。这样以后调整视觉规范时只需要改一个文件,而不是满项目替换。

另外一个很多人没意识到的好处是:在uni.scss里定义变量可以跨平台生效。uni-app编译到小程序、H5、App端时,uni.scss的注入逻辑在多个平台构建链路里是一致的,所以那些配色、字号、间距的变量在小程序里用同一套,在H5里也同一套,这比在CSS里写var(--xxx)再手动适配平台要省事得多。

2. 自动化注入机制:vite与webpack如何在编译期把变量塞进每个style

要真正理解uni.scss的“自动全局可用”,得先搞清楚构建工具在背后做了什么。我刚接触uni-app时也好奇过:为什么vue组件里的<style lang="scss">没有写@import,却能直接使用uni.scss里的变量?这背后其实是Sass编译器在编译每个style块之前,被额外追加了一段代码。

在webpack构建体系(主要是vue2版本的uni-app,或者cli方式创建的老项目)里,这个逻辑由sass-loader的additionalData配置实现。你可以把它理解为:webpack在处理每个.vue文件的style部分时,会在你的SCSS代码前面自动拼接一段@import "uni.scss";,然后再交给sass编译器。所以对你来说像是“变量凭空出现”,实际上是构建工具替你做了import。

在vite构建体系(HBuilderX 3.x版本之后创建的vue3项目)里,对应的配置点是css.preprocessorOptions.scss.additionalData,效果一样,也是每次编译scss前自动注入了uni.scss的内容。正因为是“注入”,所以它比手动@import更隐蔽也更彻底——只要是项目内的vue组件style块,全部覆盖,不会遗漏。

理解这个机制有两个实际价值:

第一,你就明白为什么uni.scss里不能写实际的CSS样式代码。比如你在uni.scss里写了一个.btn { color: red; },由于文件内容会被注入到每一个组件的style里,那么每个组件编译后都会携带一份.btn的样式定义,重复代码直接膨胀包体。uni.scss应该只放变量、mixin、函数这类“编译期产物”,不能放选择器样式。

第二,如果你用的是cli方式创建的项目,想扩展多个全局变量文件,可以通过修改vue.config.js(webpack)或vite.config.js(vite)里的additionalData配置,把别的scss文件也追加进去。我自己在维护一个中大型项目时,就把uni.scss拆成了uni.scss和_theme.scss两个文件——前者放基础token,后者放业务扩展变量,因为uni.scss是被构建工具指名注入的,所以_theme.scss需要在additionalData里手动补一条@import "@/styles/theme.scss";。

如果你在HBuilderX里创建项目,基本不需要碰这些配置,uni.scss这个约定文件已经被内置的构建配置处理好了。但知道机制仍然有用,它决定了哪些事情可以放心做、哪些事情不能做。

3. 变量怎么定:一套覆盖颜色、字号、间距、rpx换算的uni.scss配置

说回正题,在uni.scss里定义变量,最忌讳的就是东一榔头西一棒槌,今天加一个$blue,明天加一个$themeColor,到最后变量名和值自己都记不住。我建议按类别组织,并且定好一套能直接用起来的写法。

3.1 颜色体系按“语义”命名,而不是按“色值”命名

常见做法是把颜色分为品牌色、功能色、文本色、背景色、边框色几类。命名上我推荐$uni-前缀延续官方风格,方便与内置变量保持一致。比如品牌主色命名为$uni-color-primary,浅一点的辅助色命名为$uni-color-secondary,成功/警告/错误这些沿用官方语义名称。

值得多说一句的是“文本色”和“背景色”这类变量。很多项目很喜欢直接用$color-white、$color-black,看起来直接,但一旦视觉改版,这种按颜色外观命名的变量就得大量替换。我更倾向用使用场景命名,比如$uni-text-color-grey这种,它表达的是“灰色文本”的语义,而不是“灰色”,本质上你是告诉后来人“这里就是给次要文本用的颜色”。语义多过表色,后续维护会更省力。

3.2 字号、间距、圆角与阴影的梯度设计

字体大小我建议参考官方内置梯度:$uni-font-size-sm、$uni-font-size-base、$uni-font-size-lg,在这个基础上按需增加$uni-font-size-xl、$uni-font-size-xxl。间距系统比较适合做一套8像素基准的梯度:$spacing-xs: 8rpx;、$spacing-sm: 16rpx;、$spacing-md: 24rpx;、$spacing-lg: 32rpx;、$spacing-xl: 48rpx;。圆角通用等级可以分成small/medium/large,阴影则是轻、中、重三档。

有一点在实际项目里经常被我用到:在uni.scss里定义变量后,可以直接给这些变量补充!default标记。!default在SCSS里的含义是“如果这个变量之前没有被赋值,则使用当前值”。这样做的价值在于,组件库或者第三方插件里如果也定义了同名变量,它们会被你的项目值覆盖,而不会反过来把你的覆盖掉。uni-app官方文档里的内置变量大多带!default,这也是我们自定义时要保持的习惯。

3.3 rpx换算的正确姿势:以750设计稿为基准

uni-app的rpx规则是“屏幕宽度永远是750rpx”。这带来一个特别方便的换算逻辑:如果设计稿是按750px宽做的,那么在uni.scss里定义间距和尺寸变量时,直接照搬设计稿的px数值写成rpx值就行,不需要做任何除法。

比如设计稿上边距是32px,你就在uni.scss里写$spacing-page: 32rpx;,实测下来在大多数设备上渲染效果和设计稿基本一致。这个规则很适合定义一批页面级变量:$page-padding: 30rpx;、$card-radius: 16rpx;、$button-height: 88rpx;。把这些写在uni.scss里,页面里造组件时顺手就能用,不用每个页面都重新换算一遍。

需要提醒的是:如果项目里会有非750宽度的设计稿,比如有的车机屏幕或者特殊大屏场景用的是其他基准,那rpx本身的效果会不一致,这时候变量方案就要谨慎些。但对常规手机项目来说,750基准的rpx加上uni.scss变量,是性价比很高的组合。

3.4 用SCSS函数做衍生色板

比死记硬背色值更高级一点的做法,是在uni.scss里用SCSS自带函数衍生颜色。比如定义了主色$uni-color-primary: #2979ff;之后,可以顺手生成:

$uni-color-primary-light: lighten($uni-color-primary, 10%); $uni-color-primary-dark: darken($uni-color-primary, 10%); $uni-color-primary-gradient: linear-gradient(135deg, $uni-color-primary, $uni-color-primary-light);

这样做的收益是:后续如果品牌主色变了,你只需要改一个值,浅色、深色、渐变全部同步更新。我在实际项目里维护过三套主题,每套只改5个核心色值,其余十几二十个衍生变量全部自动生成,视觉一致性比手工调色好得多。

4. 这些坑我踩过:缓存不刷新、样式重复注入与多端差异

uni.scss的设计初衷非常好,但实际使用中也有几个坑,基本都是踩过之后才明白的。

4.1 改了uni.scss保存,页面样式纹丝不动

这个问题在HBuilderX和微信开发者工具的组合环境下尤其容易触发。uni.scss被构建工具注入,修改它之后,构建工具不一定能完整感知这个间接依赖的变化——尤其是HBuilderX的增量编译机制在极端情况下只会重新编译“发生变化的文件”,而uni.scss的变化可能没有正确传播到每个vue文件。

我处理这个问题的固定流程是:先停掉编译,在HBuilderX里选择“重新运行到小程序模拟器”,如果还不行,就删除项目里的unpackage目录(这是编译缓存目录),让它全量重新编译。相比之下,微信开发者工具侧的缓存很少是根因,但它也会缓存过去编译好的js和样式,稳妥起见可以把微信开发者工具里的“不校验合法域名”“ES6转ES5”这类设置检查一下,排除干扰项。

只要你确认uni.scss文件本身保存无误,全量重编之后变量一定会恢复生效。这个坑不是代码层面的问题,纯粹是缓存和增量编译机制的限制,写进项目文档里能帮团队省很多排查时间。

4.2 别在uni.scss里写任何真实样式

前面提过机制层面的原因——uni.scss会被注入到每个style块,所以一旦在里面写.page-title { font-size: 32rpx; }这类选择器样式,所有组件编译出来都会带上一份。这在H5端可能不明显,在小程序端却会导致wxss文件体积明显增大,严重时还会引起样式优先级互相干扰,出现“这个页面字体为什么变大了”的鬼畜问题。

更隐蔽的坑是:很多人会把类似@font-face的字体声明、page/body级别的全局样式误放到uni.scss里。页面级全局样式应该放在App.vue的<style>里,uni.scss只承担变量和mixin的职责。这是我强烈建议写进项目规范的一条:uni.scss不等于全局样式文件。

4.3 dart-sass兼容性:@import老用法与新语法之间的切换

uni-app从早期版本一路迭代过来,底层sass编译器版本也在升级。新版uni-app项目默认使用dart-sass,它对@import的支持开始弱化,官方更推荐@use。但uni.scss里的变量是“被注入”到组件里的,注入之后如果你在uni.scss里写了@import去引别的文件,旧语法在部分编译环境中会出现“模块化循环依赖”或“找不到变量”的报错。

我的建议是:如果项目还在用@import的写法,升级编译器或迁移到新项目时,主动把uni.scss里的@import换成@use或者直接合并文件内容。另外,新版dart-sass对lighten()、darken()这类老函数名仍然保留,但项目里大量使用它们时要注意编译器版本,有些版本会输出deprecation警告,虽然不影响运行,但还是尽早改掉比较舒服。

4.4 小程序端与H5端的CSS变量差异

用uni.scss做预编译变量,本质上是“编译期间把变量值算好”,所以最终产物里没有变量的概念,只有具体值。这在小程序端毫无问题,因为小程序的WXSS对CSS自定义属性(var(--xxx))的支持并不全面,尤其是一些老版本基础库,动态切换主题时经常失灵。而H5端不同,CSS变量是原生能力,运行时换肤非常顺畅。

这就带来一个设计层面的取舍:如果项目只面向H5,你甚至可以base在uni.scss之上再抽一层CSS变量,实现运行时动态换肤;如果项目要同时覆盖小程序和H5,最稳妥的方案还是用uni.scss做编译期变量,兼顾两端兼容性。我的实际经验是:优先用uni.scss变量做整套组件样式,等真有换肤需求时再推出基于CSS变量的运行时覆盖层,而不是一开始就依赖CSS变量。

5. 从单主题到换肤:uni.scss在深色模式与主题切换中的定位

很多项目到后期都会面临换肤或深色模式的需求。uni.scss在这里的核心定位是“定义所有可换肤的语义token”,而不是直接写死具体色值。

我在一个做过三套主题的跨端项目里是这么处理的:uni.scss里不直接写#2979ff这种具体值,而是写$brand-primary: $theme-blue-primary;这样的映射层。三个主题各有一个类似_theme-blue.scss、_theme-red.scss、_theme-dark.scss的文件,里面各自定义$theme-blue-primary等具体值。

编译时根据打包配置引入对应主题文件:

// uni.scss $uni-color-primary: $theme-primary; $uni-color-success: $theme-success; // 其余变量类似

这样做的好处是:组件代码里只跟$uni-color-primary打交道,主题切换项目里只需要换一份主题文件并重新编译。对于小程序端,运行时切换基础库的CSS变量支持有限,所以编译期就生成多套主题包是更稳的方案。

深色模式稍微特殊一些。H5端可以借助@media (prefers-color-scheme: dark)在运行时做适配,但小程序端并没有完全统一可靠的系统深色模式API。我在项目中踩过后发现:纯靠uni.scss做深色模式只能编译出深色版本,没法跟随系统自动切换。实际的项目方案通常是:小程序端在页面里手动加“深色模式开关”,编译时生成两套主题样式,通过根节点的class切换;H5端则额外叠加prefers-color-scheme媒体查询,让系统深色模式也能触发。

这个阶段uni.scss的角色是“唯一的变量源”。无论主题切换还是深色模式,最后映射到的都是这一组语义token,上游怎么变,组件代码都不用动。

6. 多人协作下样式变量的管理建议:命名、分层与变更纪律

最后聊一点团队协作层面的经验。uni.scss是全局注入的,它影响全项目的所有组件,所以它也是团队协作中“牵一发动全身”的高风险文件。多人同时改uni.scss,经常出现相互覆盖、语义冲突的问题。

我目前在项目里推行的规则主要有三点:

第一,变量命名必须带语义前缀。比如$font-开头的都是字号、$color-开头的都是颜色、$spacing-开头的都是间距。这样在组件里看到一个$color-primary-light,马上能判断它属于颜色体系且是主色的浅色变体。禁止出现$blue、$size1这类没有语义的命名。

第二,区分“基础token”和“业务变量”。基础token只管视觉体系(颜色、字号、间距、圆角),业务变量则可以有页面或模块级别的、带有业务含义的变量。比如首页头部背景色可以定义成$home-header-bg,它应该放在单独的_business.scss里,不要直接堆进uni.scss。否则uni.scss很快会变成一个大杂烩。

第三,提交变更要有注释和影响说明。我个人的习惯是:如果改动了一个核心色值,提交信息里会写明“修改主色以配合新品牌色,影响登录页、首页、个人中心共12处组件”,并且带一个自查清单——是否影响深色模式、是否需要同步改衍生色、是否影响第三方插件里的同名覆盖。这些看起来琐碎,但能避免很多“颜色悄悄变了但没人知道是谁改的”的情况。

从工具层面说,我还会给uni.scss配置eslint式的stylelint校验,加上scss语法校验规则。虽然stylelint对变量名的检查需要自定义规则,但至少能拦住$uni-color-primary重复定义这类低级错误。中小团队完全可以做到。

这套配合做完之后,uni.scss从“一个人维护的样式杂货铺”变成了“全团队共享的视觉规范源”。我在这个项目上花的时间很长,但收益也实在明显:每次视觉改版,基本改几个变量就能完事,很少再出现翻遍全项目替换色值的苦差事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询