你用 Sass 算好了 rem 值,结果用户在设置里把基础字号改了——这件事把「编译时」和「运行时」的根本差异彻底扒开了

你用 Sass 算好了 rem 值,结果用户在设置里把基础字号改了——这件事把「编译时」和「运行时」的根本差异彻底扒开了


Sass 的 @function 算出来的值,从第一天起就写死在了 CSS 文件里。构建工具跑一遍,16px→1rem,24px→1.5rem,全部算好,输出一个静态 CSS 文件。浏览器打开这个文件,里面只有结果,没有公式。

CSS @function 不一样。它算值的动作发生在浏览器里,每次 font-size 被应用的时候,它现场算。不是构建时,是运行时。

这听起来像是个技术细节,但它的影响非常具体。

一个真实场景:Sass 做不了,@function 能做。

你写了一个流体字号函数:

// Sass
@function fluid($min, $max) {
  @return clamp($min, calc($min + ($max - $min) * ((100vw - 320px) / 960)), $max);
}
h1 { font-size: fluid(1.5rem, 3rem); }

这个函数在构建时跑一遍,输出静态值。但如果用户把浏览器字号调到 150%,100vw 在 Sass 眼里还是 100vw,它不会重新算。用户的字体放大需求,在构建时就被”冻住”了。

CSS 的写法:

@function --fluid(--min, --max) returns <length> {
  result: clamp(var(--min), 50vi, var(--max));
}
h1 { font-size: --fluid(1.5rem, 3rem); }

这里的 50vi 是视口内部宽度单位,随着用户缩放实时变化。而且如果你把 --min--max 改成 CSS 变量:

:root {
  --fluid-min: 1.5rem;
  --fluid-max: 3rem;
}
h1 { font-size: --fluid(var(--fluid-min), var(--fluid-max)); }

现在改一行 CSS,整个布局系统里所有用到 --fluid() 的地方全部响应。Sass 函数根本读不到这些运行时变量,它处理的是编译时的静态值。

这不是 CSS 变量的问题,是工具链的架构差异。

Sass 函数跑在构建机器上,依赖的是项目源码和配置。CSS @function 跑在用户浏览器里,依赖的是用户实际环境:字号设置、色彩偏好、视口尺寸、媒体查询状态。

一个在「写代码的时候」算,一个在「用户看见页面的时候」算。


浏览器支持情况

@function 在 Chrome 139+ 和 Edge 139+ 已稳定,Opera 123+ 也支持。Firefox 和 Safari 暂未实现。目前全球覆盖率约 68%,还不是 Baseline 全面可用状态。

Safari/Firefox 用户会直接忽略未识别的 @function,降级为 font-size: 1.5rem 这样的静态兜底。这对可访问性不友好——用户的大字号设置完全失效了。

渐进增强写法:

/* 先写静态值兜底,所有浏览器都认 */
h1 { font-size: clamp(1.5rem, 4vw, 3rem); }

/* 有 @function 支持的浏览器,会覆盖上面这行 */
@supports (at-rule(@function)) {
  @function --fluid(--min, --max) returns <length> {
    result: clamp(var(--min), 50vi, var(--max));
  }
  h1 { font-size: --fluid(1.5rem, 3rem); }
}

@supports (at-rule(@function)) 这个检查,目前只有 Chrome 148+ 认识。这个检查本身也是一个渐进增强。


@function 还有什么 Sass 做不到的?

可以在函数体内使用 @media@container 查询,动态返回不同值:

@function --theme-font(--light, --dark) returns <length> {
  result: var(--light);
  @media (prefers-color-scheme: dark) {
    result: var(--dark);
  }
}
button { font-size: --theme-font(14px, 16px); }

Sass 的 @media 在构建时展开,不是媒体查询,是编译指令。CSS @function 里的 @media 是真正的媒体查询,浏览器每次渲染都会重新判断。


什么时候该用?

简单场景:clamp() 能解决的,不需要 @function。静态设计系统的字号,用 clamp() 写死就够了。

复杂场景:需要跨多个属性复用同一套计算逻辑,且这套逻辑需要响应运行时变量(用户设置、容器尺寸、色彩偏好)的时候,@function 是正确解法。


下一步

如果你的项目在 Chrome/Edge 用户占比高,可以直接试:

@function --rem(--px) {
  result: calc(var(--px) / 16 * 1rem);
}
.card-title { font-size: --rem(24); }

同时保留一个 Sass fallback,等 Firefox/Safari 支持后,这套写法会直接生效,不需要再改代码。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 14 阅读