资讯动态

Less变量入门到实战:样式维护与主题管理最佳实践

发布时间:2026/10/9 3:35:53 来源:尧图企业网站定制
我先从一个真实的场景聊起。我入行前端那几年维护着一个迭代了快三年的老项目。样式表里有一个品牌的蓝色散落在十几个不同的.less文件里有的写成#2d6cdf有的写成rgba(45, 108, 223, 0.8)还有的直接在上面又叠了一层filter: brightness(0.9)。后来设计说“这个蓝要再深一点点”我当时的表情可以用“天塌了”来形容。那种环境下我真正理解了 Less 变量存在的必要。所以要说“前端必须知道的 Less 基础”我第一个想聊的绝对不是语法而是变量。不是因为变量难恰恰是因为它太简单简单到很多人把它当作文档里的一行注释扫一眼就过去了。但实际写起项目来变量用得好不好直接决定了你的样式表是“能跑的烂摊子”还是“能长期维护的基础设施”。这篇内容会从 Less 变量的底层逻辑讲起再拉扯到真实项目里的命名、拆分、主题化和踩坑最后结合我自己的排查经验给出一套可以直接抄走的变量管理方式。不管你是刚写样式没多久的新人还是已经写了几年 CSS 想把手头项目从原生切到预处理器的人这篇都值得留着。1. 为什么要为样式引入“变量”这个概念从改一处颜色说起先说两个我们在日常业务开发里几乎每天都会遇到的小问题。1.1 不写变量时你是怎么“全局改色”的假设你现在接到一个需求登录页、后台框架的侧边栏、以及报表模块的表头背景色都要从原来的蓝色#2b5fa8换成新的品牌蓝#1a4d8f。原生 CSS 的做法是什么你会在项目里搜#2b5fa8去找究竟是哪些文件写在哪个类的哪个属性上然后一个一个改。如果运气好这个颜色只出现在三个地方你手动改完就结束了。但如果这个项目已经迭代了一阵子你大概率会碰上这些情况同一个颜色值有人写十六进制有人写rgb()有人写带透明度的rgba()搜索时根本搜不全。同一个语义色的实际值因为阴影、渐变、压暗、提亮衍生出了#24599b、#336bb5、#1f4f8f一堆“看起来很接近但完全不是同一个值”的兄弟色。你改了 A 页面过两天发现 B 页面的另一个背景区块没动原因是当初那个颜色是用完全不同的色值写死的。这种问题在原生 CSS 里几乎无解。你可以规范自己“以后都用 CSS 变量”但那是运行时变量在构建阶段无法参与计算而且真要维护起来还得确保每个使用的地方都能访问到那个变量作用域。但 Less 变量不一样。1.2 Less 变量的本质是一次编译期的“常量替换”Less 的变量在语言层面叫变量实际上它的工作方式更接近后端语言里的编译期常量。你写brand-blue: #2b5fa8; .nav { background: brand-blue; }编译后就是.nav { background: #2b5fa8; }Less 在构建阶段就会把brand-blue直接替换成#2b5fa8产物里没有任何运行时依赖。这意味着你在设计阶段就可以大胆地把“同一个语义、不同深浅”的颜色统一抽成变量编译完以后浏览器根本不知道你用了变量它拿到的就是干干净净的 CSS。但这不等于仅仅把重复的色值抽出来就完事了。变量的真正价值在于它替你建立了一层“语义与值”的映射关系。比如color-primary在项目里代表“品牌主色”今天它可能叫#2b5fa8明天可能是#1a4d8f业务方只关心品牌色变了不应该关心你在哪个文件里用了哪个具体值。变量就是承担这个映射的载体。如果你现在还在原生 CSS 里靠“全局搜索颜色值”来维护主题我强烈建议你先从引入 Less 变量开始做重构。即使暂时不想大动架构把这个映射关系建立起来收益都是立竿见影的。1.3 一个反直觉的事实CSS 自定义属性替代不了 Less 变量有的同学会问CSS 也有var(--brand-blue)为什么还要专门学 Less 变量我的判断是两者定位不同不是替代关系。CSS 自定义属性是浏览器运行时获取值它适合做“主题运行时切换”比如夜间模式、用户自定义主题因为你可以通过 JS 随时改--xxx的值页面马上变色不用重新编译。Less 变量是在编译阶段就把值写死产物里没有变量名适合构建时定死设计规范或者配合多环境打包输出不同主题。所以如果你听到别人说“CSS 变量已经能取代 Less 变量了”那基本是混淆了这两个概念。真实项目里两者经常是配合使用的Less 变量定义设计基元色板、字号、间距然后在关键部位导出成 CSS 自定义属性运行时再拿一部分出来做动态主题。这块到后续章节我再展开。2. Less 变量的定义、引用与作用域从一行语法到完整的规则既然开了头我就把基本语法和规则一次讲透特别是那些文档里不够醒目的坑。2.1 变量声明的基本姿势与“默认值”写法Less 里变量用开头声明方式跟写属性差不多primary-color: #1a4d8f; font-size-base: 14px; space-md: 16px;使用的时候直接引用变量名.btn { color: primary-color; font-size: font-size-base; padding: space-md; }编译结果.btn { color: #1a4d8f; font-size: 14px; padding: 16px; }注意一点Less 的变量值可以是几乎任何 CSS 值颜色、字符串、数字、带单位的值、甚至整段属性值都可以。你甚至可以这样写btn-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); .card { box-shadow: btn-shadow; }很多同学刚接触 Less 时会问变量能定义变量吗能。而且我后面会讲到一个很实用的“变量拼接变量名”玩法但那是进阶内容不建议新手一开始就用可读性会下降。如果要给变量设默认值Less 没有像 Sass 那样的!default声明但实现思路一致。我常用的方式是在主题变量文件里先用一组设计基元值兜底业务组件再通过变量覆盖来实现定制// base-theme.less primary-color: #1a4d8f; btn-radius: 4px;这样引入基础文件后如果项目里想局部覆盖只需在后面重新声明同名变量即可。Less 的特点是“后定义覆盖先定义”这一点在 2.3 里会展开。2.2 变量插值的四个高频位置变量不仅能直接作为属性值还能拼接到选择器名、属性名、URL 和媒体查询特性中。我用四个例子说明最常见的场景。例一选择器插值size: large; .btn-{size} { padding: 16px 32px; }编译后.btn-large { padding: 16px 32px; }这在写组件库的“尺寸变体”时非常香从一个变量就能衍生出一组规则。例二属性名插值side: left; .box { margin-{side}: 20px; }应用场景主要是需要批量生成 shift 类工具样式平时业务代码里我其实不太推荐用因为可读性比较差搜索也不方便。例三URL 字符串插值image-path: ../images; .logo { background: url({image-path}/logo.png); }这里有个经典坑Less 变量的值如果包含../这类相对路径拼接时很容易因为编译上下文与最终 CSS 文件所在目录不一致导致图片 404。后面第 4 节我会专门说排查过程。例四媒体查询特性插值phone: ~(max-width: 767px); media phone { .container { padding: 12px; } }这里的~是 Less 的“转义”操作本质上告诉 Less“这一段你别瞎解析直接原样输出”。很多老项目里会频繁出现带~的写法它也是解决某些边界场景的钥匙。2.3 作用域规则Lazy Loading 与“后声明先覆盖”的时序Less 的作用域和原生 CSS 的作用域不太一样我当年因为没搞明白踩过一整个下午的坑。Less 遵循“就近作用域后声明覆盖先声明”的原则但它的解析方式是延迟加载。也就是说变量不用像 JS 那样先声明后使用Less 会先把整个作用域扫描完再用最后的值当作最终值。color: #333; .text { color: color; } color: #555;最终编译结果.text { color: #555; }你没看错变量声明虽然在.text之后但 Less 在解析时拿到了作用域里最后定义的那个值所以结果不是#333而是#555。这在 Sass 里是绝对不允许的Sass 要求先定义后使用。Less 这种风格给了一个自由度你可以把一组主题变量全部放在文件后面前面安心调用。但延迟加载同样带来副作用。如果你在一个嵌套块内部定义了同名变量它会就近覆盖外层color: #333; .text { color: #999; color: color; } .author { color: color; }编译后.text是#999.author还是#333。这种局部变量覆盖全局变量的逻辑写嵌套样式时非常实用但也容易让新人困惑为什么我用全局变量时值老是乱变答案往往是你在某个嵌套作用域里先后声明过同名变量。所以我的建议是全局设计基元变量色板、字号、间距永远放在独立变量文件顶部业务组件内不要再重新定义同名变量。如果要覆盖请起一个带有业务前缀的新变量别图省事直接改 color。2.4 变量拼接与“变量名中套变量名”Less 支持通过{name}的方式对变量名做插值这个能力在做主题体系时特别有用。theme: dark; color-dark: #000; color-light: #fff; .card { color: color-{theme}; }这里color-{theme}解析的过程是先拼出变量名color-dark然后取它的值#000。这个玩法在组件库级项目里能节省大量重复代码但对新手而言是个阅读负担建议在普通业务项目里适度使用别把代码写成谜语。3. 真实项目里的变量组织与管理从零搭一套可持续的变量体系语法熟练之后下一个阶段就要回答变量那么多怎么放、怎么命名、怎么避免后期变成第二套烂摊子。3.1 变量文件的拆分不要把几百个变量塞进一个文件我见过不少项目把所有变量堆在一个variables.less里足足四百多行。这种文件不是不能写是真到后期维护时脑袋会炸。光是一个主蓝色文件里就能有blue、main-blue、primary、blue-500、brand-blue五种名字改起来谁都不敢动。我更推荐按层级拆分文件职责示例tokens.less设计基元是最底层原子值blue-500: #1a4d8f;semantic.less语义变量映射到业务场景color-primary: blue-500;theme.less主题切换相关变量bg-page: color-primary;/components/*.less按组件维度组织的局部变量btn-radius: 4px;层级关系可以理解成“基元 - 语义 - 业务实现”。原则是上层可以引用下层反向则不推荐。这样当你某天要改品牌色只需要动tokens.less其他文件几乎不用动。3.2 命名规范决定了后续能不能搜得到命名这件事我吃了很多亏现在总结下来三条经验按语义命名不按显示效果命名。blue这种名字今天指#1a4d8f明天指#2b5fa8后天指什么都不确定。建议用color-primary、color-success、color-warning这种语义名。用前缀分组。color-、font-、space-、radius-、shadow-。这样无论是写代码还是全局搜索都能一眼认出变量归属。带数字后缀时从 50 到 900 留好中间档位。比如blue-300、blue-500、blue-700。真等哪天设计给你一个“比 500 浅一点点但不到 400”的颜色你就能插一个blue-450进去而不是把 500 的引用全改掉。命名这种东西没有绝对正确但一套有共识的规范能让同事之间的协作成本低一大截。3.3 基于变量实现多主题切换的两个层级业务里经常遇到灰度测试或不同端不同主题的需求。Less 变量此时能做的有两层第一层静态多主题。针对不同的打包入口引入不同的主题变量文件。比如// entry/main.less import themes/light.less; import app.less;// entry/dark.less import themes/dark.less; import app.less;这样打包出来两套不同主题的 CSS各自把变量值编译进去。适合活动页、品牌差异明显的两个独立站点。第二层运行时与 CSS 自定义属性对接。如果要做“同一页面热切换”Less 变量帮不上运行时因为它的值是编译期写死的。这时候可以把 Less 变量导出成 CSS 自定义属性再在运行时动态改 CSS 变量:root { --color-primary: color-primary; --bg-page: bg-page; }JS 里改变--color-primary的值页面主色会立刻变化而其他基于 Less 变量的静态部分依然保持不变。这是目前很多中后台系统做“主题换肤”的主流方案。理解了前面说的编译期与运行时的区别你会发现这个方案很自然。3.4 少写死用变量配合运算与颜色函数变量不只是值本身配合 Less 内置函数能产出很多衍生值避免你为了一个浅一点的背景色再发明一个新变量名。常用场景包括lighten(color-primary, 10%)得到主色的浅色背景。darken(color-primary, 5%)得到 hover 态的主色。fade(color-primary, 20%)得到主色的透明版本。宽度、间距通过变量直接计算grid-gutter * 2。color-primary: #1a4d8f; .btn { background-color: color-primary; :hover { background-color: darken(color-primary, 6%); } }这样设计给出的#1a4d8f程序自动算出 hover 态的颜色视觉上永远是同一个色系不会出现品牌色旁边跟着一个不相干的深灰色。4. 变量用久了之后那些最容易翻车的坑与排查链路学习阶段总觉得变量就这么点事真正项目跑起来才发现坑藏在细节里。下面按我真实踩过的顺序列出来。4.1 坑一懒加载导致变量值与我预期的完全相反有一次我接手一个老项目发现侧边栏背景色怎么调都不对。代码结构大概是import theme.less; .sidebar { background: bg-sidebar; } bg-sidebar: #2b5fa8;但theme.less里也定义了一个bg-sidebar: #1a4d8f;。Less 的延迟加载规则会让bg-sidebar采用文件里最后一次声明的值。因为import theme.less在顶部而后面的显式声明bg-sidebar: #2b5fa8在更后面于是侧边栏最终变成了#2b5fa8而不是我以为的主题文件里的#1a4d8f。排查链路如下先查看编译后的 CSS确认侧边栏实际生效色值是什么。在.sidebar选择器所在作用域里搜索所有bg-sidebar声明。按 Less 后声明覆盖先声明的规则找到最终值来源。发现是import之后又有人写了同名覆盖。这类问题用 IDE 全局搜索同一个变量名其实很容易发现。关键是养成习惯全局变量名不要在不同文件里反复声明。如果一定要覆盖就在同一份主题变量文件里统一完成。4.2 坑二作用域不是“层叠”不会沿 DOM 继承原生 CSS 里父级定义了属性子级会继承。但 Less 变量没有“继承”这个说法它是词法作用域。color: #333; .parent { color: #999; } .child { color: color; }你以为.child会得到#999因为“在 DOM 结构里它是.parent的孩子”。实际上.child和.parent是平级的编译上下文Less 不会因为 DOM 嵌套就把变量传下去。结果是#333。这个坑在写嵌套组件时特别容易出。想明白 Less 变量在编译期就已经替换完跟浏览器运行时完全无关就不会再混淆了。4.3 坑三字符串插值里的相对路径 404常见于背景图、字体文件路径。某个页面从登录模块挪到了个人中心模块结果图片地址全都 404。排查过程大致是打开编译后的 CSS发现background: url(../images/logo.png)。页面所在路径变成http://abc.com/user-center/page浏览器解析相对路径后得到/user-center/images/logo.png。实际图片放在/images/logo.png。这里本质上是 CSS 的相对路径与页面路由路径不匹配。正确处理方式是在 Less 变量里定义一个绝对路径基础比如/assets/images/。或者用构建工具的 publicPath 统一处理静态资源路径。image-path: /assets/images; .logo { background: url({image-path}/logo.png); }这样无论页面路由多深图片路径始终从根目录出发不会再被路由层级带着跑偏。4.4 坑四与 CSS 原生变量混用边界模糊项目里同时存在 Less 变量和 CSS 自定义属性时容易出现一种混乱开发者在 Less 变量里放了一段计算表达式希望运行时也跟着变。base-size: 14px; font-size-large: base-size * 1.2;Less 编译后font-size-large会被直接算成16.8px。这是纯编译期结果。但如果某个元素期望“用户调整基础字号时大号字体跟着变”那就得用 CSS 变量:root { --base-size: 14px; } .text-large { font-size: calc(var(--base-size) * 1.2); }两者本质区别我在第 1 节说过这里再补一句凡是需要运行时可变的交给 CSS 变量凡是构建期就能确定的优先用 Less 变量。混用不可怕可怕的是分不清边界。4.5 坑五用变量把项目“包”成了一个黑盒最后一个是运维维护层面的体会。变量用得太狠会出现一种现象打开样式文件满屏都是xxx根本不知道最终渲染成什么颜色、什么尺寸。想调试一个按钮必须顺着变量名一路查到 token 文件。我的建议是给可读性留一条底线业务组件里基础属性和关键视觉值可以直接用语义变量但不要在一个规则里套三层层层映射。btn-bg直接等于color-primary可以接受btn-bg等于colors[primary]再等于theme[base]这种嵌套方式能不用就不用。变量名尽量带上足够信息看到btn-radius-lg大概就能猜到是“大圆角按钮”而不是看到一个r1。好的变量体系和好的代码一样最终目标是让三个月后的自己、或者新来的同事不需要通过考古才能改样式。5. 我在真实项目里沉淀的一套变量组织模板前面聊了这么多理念和坑最后分享一套我目前在用的模板可以直接作为参考。// tokens.less —— 设计基元 blue-500: #1a4d8f; blue-600: #163f73; blue-100: #e6f0fb; gray-500: #666; gray-300: #ccc; font-size-xs: 12px; font-size-sm: 13px; font-size-md: 14px; font-size-lg: 16px; space-sm: 8px; space-md: 16px; space-lg: 24px; radius-sm: 2px; radius-md: 4px; radius-lg: 8px; // semantic.less —— 语义映射 color-primary: blue-500; color-primary-hover: blue-600; color-primary-light: blue-100; color-text-main: gray-500; color-text-secondary: gray-300; font-size-base: font-size-md; spacing-base: space-md; border-radius-base: radius-md; // theme.less —— 具体场景 bg-page: #f5f6fa; bg-sidebar: color-primary; bg-card: #fff; text-color-on-primary: #fff; // component/button.less —— 组件局部变量 btn-bg: color-primary; btn-bg-hover: color-primary-hover; btn-radius: border-radius-base;实际项目里我通常再配一个index.lessimport tokens.less; import semantic.less; import theme.less;这样每个业务组件只需要import (reference) ../theme.less;就可以引用全部变量。注意(reference)可以减少编译产物体积优化构建性能。这套模板用到现在的感受是设计同学改一个主色值我只需要在tokens.less里动一行按钮 hover 态不用额外值Less 算出来所有组件引用同一个语义变量不会出现“同一个主色五种色值”这种事。真要说麻烦也就是前期把变量打通需要花点时间但绝对比后期追着色值满项目跑要省力得多。如果你正打算把手里老项目的样式表切成 Less我建议从今天开始第一步就是停下来别再往样式里写第四个#1a4d8f了。把第一个变量定义出来你就算是真正摸到了 Less 的入口。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑