Sass、Less 这类 CSS 预处理器解决了什么问题?原生 CSS 现在能替代它们吗?
预处理器在构建时把带变量、嵌套、混入、函数的样式编译成普通 CSS,解决了早期 CSS 难以复用和组织的问题。现在原生 CSS 已经有自定义属性、嵌套、@layer 和 color-mix() 等能力,很多场景不再需要预处理器;但编译期的循环、条件和模块化导入仍是预处理器的优势。
简答
早年写 CSS,品牌色
#1677ff要在几十个地方复制粘贴,改一次颜色得全局搜索替换;按钮的悬停、禁用样式要重复写一长串选择器。Sass、Less 这类预处理器就是为此出现的。按 Sass 文档的说法,它是一种编译成 CSS 的样式语言,可以使用变量、嵌套规则、混入、函数等功能。它们的工作方式是:你写
.scss或.less文件,构建时由编译器转换成浏览器认识的普通 CSS。所以变量、嵌套这些只在编译阶段存在,浏览器最后拿到的 CSS 里已经没有了。现在原生 CSS 补上了很多能力。自定义属性
--brand-color可以代替变量,而且它是运行时的,能被 JavaScript 修改、能按主题切换,这是预处理器变量做不到的;CSS 嵌套已经被现代浏览器支持,MDN 说它和预处理器的区别在于由浏览器解析,不需要预先编译;@layer管理层叠优先级,color-mix()能在 CSS 里混合颜色。要注意,预处理器仍有原生 CSS 没有的东西:编译期的
@each、@if循环和条件,可以批量生成工具类;@use的模块系统可以拆分文件、避免命名冲突。新项目可以先看原生 CSS 是否够用,已有的预处理器项目也不必急着迁移。
同一段样式两种写法
// Sass
$brand: #1677ff;
@mixin focus-ring {
outline: 2px solid $brand;
outline-offset: 2px;
}
.button {
background: $brand;
&:hover { background: darken($brand, 8%); }
&:focus-visible { @include focus-ring; }
}
/* 原生 CSS */
:root { --brand: #1677ff; }
.button {
background: var(--brand);
&:hover { background: color-mix(in srgb, var(--brand), black 8%); }
&:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
}
}
两者效果接近,差别在什么时候计算。Sass 的 $brand 和 darken() 在编译时就算成了固定的颜色值,CSS 文件里写死了 #1677ff。原生的 var(--brand) 在浏览器里才取值,给 [data-theme='dark'] 重新定义 --brand,所有用到它的地方立刻跟着变,切换主题不需要重新编译。
Sass 官方文档说 darken() 只是把亮度减去固定的量,往往不是想要的效果,所以它没有被放进新的模块系统,将来还会被移除;要让颜色按百分比变暗,推荐用 color.scale()。原生的 color-mix() 写法不同,结果也不一定和 darken 完全一样,迁移时要对照效果。
@mixin 在原生 CSS 里没有直接对应的写法。可以把公共样式写成一个类名,或者用自定义属性传参数。
PostCSS 和预处理器是什么关系
PostCSS 不是预处理器,它是一个处理 CSS 的工具平台,本身不做任何转换,由插件完成具体工作。常用的 Autoprefixer 根据目标浏览器自动加前缀;postcss-preset-env 让你提前使用新的 CSS 语法,再转换成目标浏览器支持的写法。很多项目里两者一起用:Sass 负责编写阶段的便利,PostCSS 负责兼容性处理。
常见追问
CSS Modules 和预处理器冲突吗?
不冲突。CSS Modules 解决的是类名作用域问题,把类名编译成唯一的名字,避免组件之间样式冲突;预处理器解决的是写法问题。可以写
Button.module.scss,两者同时生效,样式组织方式见《CSS Modules、层叠层和自定义属性怎样帮助维护样式?》。
Sass 的 @import 为什么不推荐了?
Sass 已经弃用
@import,推荐@use和@forward。@import会把所有变量、混入放进全局,多个文件之间容易互相覆盖,同一个文件被导入多次还会重复输出 CSS;@use让每个文件成为独立的模块,用命名空间访问,只会加载一次。