Sass 运算 vs CSS calc():你的计算该放在哪一层?

53 阅读6分钟

面对一张 960px 宽的设计稿,主内容区 600px,侧边栏 300px,你心算了一下——62.5% 和 31.25%,然后把这几个百分比硬编码进了 CSS。

三个月后,设计稿宽度调整为 1200px。你不得不翻出计算器,把所有比例重新算一遍。

于是你想到 Sass 的数学运算,把设计稿的像素值直接写进代码,让机器自动算出百分比。但 CSS 也有 calc(),它不也能在样式里写表达式吗?calc(600px / 960px * 100%) 不就等于 62.5% 吗?为什么还需要 Sass?

问题的关键,在于计算到底发生在哪一层。本文将深入解析 Sass 运算符的用法与变革,并对比 CSS calc(),彻底讲清“编译时计算”与“运行时计算”的本质区别,帮你做出正确的技术选择。

Slide 4_3 - 5.png

一、Sass 支持的数学运算符

Sass 提供了一套标准运算符,让你可以像写程序一样在样式表中做计算:

运算符含义示例
+加法width: 100px + 20px;120px
-减法width: 100px - 20px;80px
*乘法width: 100px * 2;200px
math.div()除法math.div(100px, 2)50px
%取模10 % 31

⚠️ 注意:除法是 math.div(),不是 /。这恰恰是“编译时计算”与 CSS 原生语法划清界限的关键一步。

二、除法为什么变成了 math.div()?

在 CSS 中,/ 是合法的属性值分隔符。例如:

font: 12px/1.5 Helvetica; /* 字号12px,行高1.5 */

如果 Sass 继续把 / 当作除法运算符,编译器将无法区分你的意图——这到底是 CSS 的字体简写,还是一次数学运算?

这种歧义正是“编译时层”与“标准层”的冲突。Sass 在编译时要对源码做运算,而原生 CSS 允许 / 作为字面量存在。为了把两个层级彻底解耦,Sass 正式废弃了 / 除法,要求使用 math.div() 函数:

@use "sass:math"; // 必须引入内置模块

// ❌ 旧式写法(已弃用)
width: (600px / 960px) * 100%;

// ✅ 新式标准写法
width: math.div(600px, 960px) * 100%;

这一变化让代码意图一目了然:凡是看到 math.div(),你就知道这是在 Sass 层做除法,而不是把有歧义的表达式留给浏览器。

三、实例剖析:构建基于 960px 的流式网格

来看一个 Sass 官网的流体网格示例:

@use "sass:math";

.container {
  display: flex;
}

article[role="main"] {
  width: math.div(600px, 960px) * 100%;
}

aside[role="complementary"] {
  width: math.div(300px, 960px) * 100%;
  margin-left: auto;
}

编译后的 CSS

.container {
  display: flex;
}

article[role=main] {
  width: 62.5%;
}

aside[role=complementary] {
  width: 31.25%;
  margin-left: auto;
}

背后发生了什么?

整个过程分两步:

  1. 像素相除,单位抵消
    math.div(600px, 960px) → 600 ÷ 960 = 0.625(无单位纯数字)
  2. 数字乘以百分比
    0.625 * 100%62.5%

同样,300 ÷ 960 = 0.3125,乘以 100% 得到 31.25%。

最终 CSS 中没有一丁点 Sass 运算的痕迹,只有浏览器可以直接使用的静态百分比值。这说明:计算已经在编译层完成了,浏览器根本感知不到曾经有过任何数学表达式。

这带来了什么好处?

  • 设计稿直译:设计师标注“总宽 960px,主内容 600px”,你直接写 math.div(600px, 960px),不用动脑换算。
  • 一改全改:如果总宽从 960 变成 1200,只需把源码中的 960px 替换为 1200px,所有比例自动重算,告别手动维护。

四、运算中的单位处理规则

Sass 对单位的处理非常“物理”,理解这些规则可以避免踩坑。

运算场景规则示例结果
相同单位相除单位抵消,得纯数字math.div(10px, 5px)2
不同单位相加不允许,直接报错10px + 5em❌ 编译错误
数字乘带单位值单位保留2 * 10px20px
数字乘百分比得百分比0.5 * 100%50%
百分比除数字百分比保留math.div(100%, 2)50%

五、Sass 运算 vs CSS calc():计算该放在哪一层?

现在回到核心问题:为什么不用 calc()?它不也能算出 62.5% 吗?

calc() 确实能做同样的数学运算,但它和 Sass 运算处在完全不同的层。

对比维度Sass 数学运算CSS calc()
计算时机编译时(开发阶段)运行时(浏览器渲染)
输出结果静态固定值(如 62.5%)表达式保留,浏览器实时计算
动态性❌ 只能基于源码中的已知值✅ 可混用不同单位、CSS 变量
性能开销微小,但复杂度高时可能累积
适用场景设计标准固定、预计算动态混合视口、容器查询等

5.1 什么时候用 Sass 运算?

当计算依据在设计阶段就确定不变时,用 Sass 在编译期算好,输出静态值:

// 总宽固定 960,永远不会变 → Sass 运算
width: math.div(600px, 960px) * 100%; // 输出 62.5%

浏览器收到的就是 62.5%,没有任何表达式需要解析。

5.2 什么时候用 calc()?

当计算依赖运行时才能确定的值时,用 calc() 交给浏览器:

/* 依赖视口宽度,只有浏览器知道 → calc() */
width: calc(100vw - 2rem);

/* 依赖 CSS 变量,运行时变化 → calc() */
width: calc(var(--content-width) * 0.6);

5.3 两者互补

  • 值在设计阶段已知 → Sass 运算
  • 值在运行时才确定 → CSS calc()

二者不是替代关系,而是互补。事实上,你还可以在 Sass 里写 calc(),将编译时值与运行时值混合使用,比如:

.element {
  width: calc(100% - #{$sidebar-width});
}

这样,$sidebar-width 在编译时被替换为具体值,而 100% 由浏览器动态计算。你在 Sass 层处理静态部分,把动态部分留给浏览器——各司其职,边界清晰。

六、其他运算符的实际场景

官网示例聚焦于乘法和除法,但其他运算符同样高频。

6.1 +-:动态计算间距

$spacing-base: 8px;
$spacing-lg: $spacing-base + 8px;
$spacing-sm: $spacing-base - 2px;

.card {
  padding: $spacing-lg $spacing-base;
  margin-bottom: $spacing-base * 3;
}

6.2 % 取模:循环中判断奇偶

@use "sass:math";

@for $i from 1 through 6 {
  .col-#{$i} {
    width: math.div(100%, 6) * $i;
    
    @if $i % 2 == 0 {
      background: #f0f0f0; // 偶数列加背景
    }
  }
}

6.3 封装成函数:让复用更优雅

@use "sass:math";

// 将比例计算封装为函数
@function col-width($col, $total: 960px) {
  @return math.div($col, $total) * 100%;
}

.main { width: col-width(600px); }  // 62.5%
.side { width: col-width(300px); }  // 31.25%

七、总而言之

Sass 的运算符体系,本质上是在做一件事:把设计阶段已知的数值计算,提前到编译时完成,最终输出纯净、无冗余、高性能的静态 CSS。

当我们面对“用 Sass 算还是用 calc() 算”的疑问时,本质上是在问:这个计算依赖的是静态设计稿,还是动态的浏览器环境?

  • 如果答案在设计稿上——请把它放在 Sass 编译层,用 math.div() 等运算符完成预计算,还浏览器一个干净的静态值。
  • 如果答案要到运行时才能揭晓——请使用 CSS calc(),让它待在浏览器层,动态响应变化。

掌握这个分层思维,你就能在正确的层级做正确的计算,让源码保持直观可维护,让输出保持高性能零开销。