CSS
CSS有一个特殊的calc()函数,用于做基本的数学运算。下面是一个例子:

  1. .main-content {
  2. /* Subtract 80px from 100vh */
  3. height: calc(100vh - 80px);
  4. }

来了解一下关于这个非常有用的功能所需要了解的一切。

calc() 只作用于属性值

唯一可以使用calc()函数的地方是在值中。请看这些例子,在这些例子中设置了一些不同属性的值。

  1. .el {
  2. font-size: calc(3vw + 2px);
  3. width: calc(100% - 20px);
  4. height: calc(100vh - 20px);
  5. padding: calc(1vw + 5px);
  6. }

它也可以仅用于部分属性,例如:

  1. .el {
  2. margin: 10px calc(2vw + 5px);
  3. border-radius: 15px calc(15px / 3) 4px 2px;
  4. transition: transform calc(1s - 120ms);
  5. }

它甚至可以成为构成属性一部分的另一个功能的一部分!例如,这里的calc() 用于渐变的色标中

  1. .el {
  2. background: #1E88E5 linear-gradient(
  3. to bottom,
  4. #1E88E5,
  5. #1E88E5 calc(50% - 10px),
  6. #3949AB calc(50% + 10px),
  7. #3949AB
  8. );
  9. }

calc() 用于长度和其他数值

请注意,以上所有示例本质上都是基于数字的。会讲到一些数的使用注意事项(因为有时不需要单位),但这是针对数字的数学,而不是字符串之类的东西。

  1. .el {
  2. /* Nope! */
  3. counter-reset: calc("My " + "counter");
  4. }
  5. .el::before {
  6. /* Nope! */
  7. content: calc("Candyman " * 3);
  8. }

CSS有很多长度,它们都可以与calc() 一起使用:

  • px
  • %
  • em
  • rem
  • in
  • mm
  • cm
  • pt
  • pc
  • ex
  • ch
  • vh
  • vw
  • vmin
  • vmax

无单位的数字也是可以接受的,例如line-height:calc(1.2 * 1.2); 以及诸如transform:rotate(calc(10deg * 5));之类的角度。
也可以不执行任何计算并且仍然有效:

  1. .el {
  2. /* Little weird but OK */
  3. width: calc(20px);
  4. }

不能在媒体查询中使用

当正确使用calc()时(长度单位作为属性的值),可悲的是,当应用于媒体查询时,calc()将无法工作。

  1. @media (max-width: 40rem) {
  2. /* Narrower or exactly 40rem */
  3. }
  4. /* Nope! */
  5. @media (min-width: calc(40rem + 1px)) {
  6. /* Wider than 40rem */
  7. }

有一天,这将是很酷的,因为可以用一种相当合理的方式(如上)进行相互排斥的媒体查询。

混合单位

这也许是calc()最有价值的功能! 几乎上面的每一个例子都已经做到了这一点,但只是为了强调一下,这里是将不同的单位混合在一起。

  1. /* Percentage units being mixed with pixel units */
  2. width: calc(100% - 20px);

这是说。 元素的宽度,减去20个像素就可以了。
在流体宽度的情况下,完全没有办法单独用像素来预计算这个值。换句话说,不能用Sass这样的东西来预处理calc(),因为它是一个试图完成的polyfill。不是说需要这样做,因为浏览器的支持很好。但问题是,当用这种方式混合单位时,必须在浏览器中完成(在 “运行时”),这也是calc()的大部分值。
下面是其他一些混合单位的例子。

  1. transform: rotate(calc(1turn + 45deg));
  2. animation-delay: calc(1s + 15ms);

这些可能会被预处理,因为它们混合了与运行时确定的任何单位都不相关的单位。

与预处理器数学比较

刚刚介绍了无法预处理calc()可以执行的最有用的操作。但是有一点重叠。例如,Sass内置了数学功能,因此可以执行以下操作:

  1. $padding: 1rem;
  2. .el[data-padding="extra"] {
  3. padding: $padding + 2rem; // processes to 3rem;
  4. margin-bottom: $padding * 2; // processes to 2rem;
  5. }

甚至带有单位的数学也可以在这里工作,将相同单位的值相加或乘以无单位数。但是不能混合使用单位,并且它与calc()有类似的限制(例如,乘和除必须使用无单位的数字)。

显示数学

即使没有使用只有calc()才能实现的功能,也可以用它在CSS里面 “展示你的工作”。例如,假设需要精确计算一个元素的1⁄7的宽度……。

  1. .el {
  2. /* This is easier to understand */
  3. width: calc(100% / 7);
  4. /* Than this is */
  5. width: 14.2857142857%;
  6. }

这可能会在某种自创的CSS API中泛起,好比。

  1. [data-columns="7"].col { width: calc(100% / 7); }
  2. [data-columns="6"].col { width: calc(100% / 6); }
  3. [data-columns="5"].col { width: calc(100% / 5); }
  4. [data-columns="4"].col { width: calc(100% / 4); }
  5. [data-columns="3"].col { width: calc(100% / 3); }
  6. [data-columns="2"].col { width: calc(100% / 2); }

calc()的数学运算符

已经有了+、-、*和/,但它们在使用方法上有所不同。

加法(+)和减法(-)要求这两个数都是长度

  1. .el {
  2. /* 有效👍 */
  3. margin: calc(10px + 10px);
  4. /* 无效的👎 */
  5. margin: calc(10px + 5);
  6. }

无效的值会使整个单项声明无效。

除法(/)要求第二个数字是无单位的。

  1. .el {
  2. /* 有效的👍 */
  3. margin: calc(30px / 3);
  4. /* 无效的👎 */
  5. margin: calc(30px / 10px);
  6. /* 无效的👎 (不能除以0) */
  7. margin: calc(30px / 0);
  8. }

乘法(*)要求其中一个数是无单位的。

  1. .el {
  2. /* 有效的 👍 */
  3. margin: calc(10px * 3);
  4. /* 有效的 👍 */
  5. margin: calc(3 * 10px);
  6. /* 无效的 👎 */
  7. margin: calc(30px * 3px);
  8. }

空白空间很重要

对于加法和减法来说是这样的。

  1. .el {
  2. /* 有效的 👍 */
  3. font-size: calc(3vw + 2px);
  4. /* 无效的 👎 */
  5. font-size: calc(3vw+2px);
  6. /* 有效的 👍 */
  7. font-size: calc(3vw - 2px);
  8. /* 无效的 👎 */
  9. font-size: calc(3vw-2px);
  10. }

负数是可以的(例如 calc(5vw - -5px)),但这是一个例子,说明空格不仅是必需的,而且是有用的。
+和-周围需要间隔的原因其实是出于解析的考虑。但例如,2px-3px被解析为数字 “2 “和单位 “px-3px”,这对任何人都没有好处,而+还有其他问题,比如被 “数字语法消耗”。
乘法和除法不需要运算符周围的空格。但一般建议是,为了其他运算符的可读性和肌肉记忆,应该包括这些空格。
外围的空白并不重要。如果愿意,甚至可以做换行符。

  1. .el {
  2. /* 有效的 👍 */
  3. width: calc(
  4. 100% / 3
  5. );
  6. }

不过,请注意以下事项:calc() 与开头括号之间没有空格。

  1. .el {
  2. /* 无效的 👎 */
  3. width: calc (100% / 3);
  4. }

嵌套计算(calc())

可以这样做,但没有必要。这和使用一组额外的小括号而不使用 calc()部分是一样的。例如

  1. .el {
  2. width: calc(
  3. calc(100% / 3)
  4. -
  5. calc(1rem * 2)
  6. );
  7. }

不需要在calc() 内部使用这些代码,因为括号可以单独工作:

  1. .el {
  2. width: calc(
  3. (100% / 3)
  4. -
  5. (1rem * 2)
  6. );
  7. }

而在这种情况下,即使没有括号,”运算顺序 “也能工作。也就是说,除法和乘法是先发生的(在加法和减法之前),所以根本不需要括号。可以这样写:

  1. .el {
  2. width: calc(100% / 3 - 1rem * 2);
  3. }

但是,如果想增加清晰度,请随意使用。如果操作顺序不利于(例如,确实确实需要先进行加法或减法),则需要括号。

  1. .el {
  2. /* This */
  3. width: calc(100% + 2rem / 2);
  4. /* Is very different from this */
  5. width: calc((100% + 2rem) / 2);
  6. }

CSS自定义属性和calc() 🎉

除了calc()能够混合单位的惊人能力之外,calc()的下一个最棒的地方就是与自定义属性一起使用。自定义属性可以有一些在计算中使用的值。

  1. html {
  2. --spacing: 10px;
  3. }
  4. .module {
  5. padding: calc(var(--spacing) * 2);
  6. }

可以想象一个CSS设置,通过设置一堆CSS自定义属性,然后让CSS的其他部分根据需要使用它们,大量的配置发生在顶部。
自定义属性也可以相互引用。这里有一个例子,其中使用了一些数学(注意一开始没有calc()函数),然后再应用。(最终还是要放在calc()里面。)

  1. html {
  2. --spacing: 10px;
  3. --spacing-L: var(--spacing) * 2;
  4. --spacing-XL: var(--spacing) * 3;
  5. }
  6. .module[data-spacing="XL"] {
  7. padding: calc(var(--spacing-XL));
  8. }

需要记住使用属性的地方的calc(),但从可读性的角度来看,这是可能的,而且可能很有趣。
自定义属性可以来自HTML,这有时是一件很酷很有用的事情。

  1. <div style="--index: 1;"> ... </div>
  2. <div style="--index: 2;"> ... </div>
  3. <div style="--index: 3;"> ... </div>
  1. div {
  2. /* Index value comes from the HTML (with a fallback) */
  3. animation-delay: calc(var(--index, 1) * 0.2s);
  4. }

以后增加单位

如果在存储数字时没有单位,或者提前用没有单位的数字做数学运算,总是可以等到应用数字时,通过乘以1和单位来加单位。

  1. html {
  2. --importantNumber: 2;
  3. }
  4. .el {
  5. /* Number stays 2, but it has a unit now */
  6. padding: calc(var(--importantNumber) * 1rem);
  7. }

弄乱颜色

像RGB和HSL这样的颜色格式有数字,可以用calc()来搞。例如,设置一些基本的HSL值,然后改变它们形成一个自己创造的系统(例子)。

  1. html {
  2. --H: 100;
  3. --S: 100%;
  4. --L: 50%;
  5. }
  6. .el {
  7. background: hsl(
  8. calc(var(--H) + 20),
  9. calc(var(--S) - 10%),
  10. calc(var(--L) + 30%)
  11. )
  12. }

不能把calc()attr()结合在一起

CSS中的attr()函数看起来很吸引人,就像可以从HTML中提取属性值并使用它们。但是…

  1. <div data-color="red">...</div>
  1. div {
  2. /* Nope */
  3. color: attr(data-color);
  4. }

不幸的是,这里没有 “类型 “在起作用,所以attr()的唯一作用是与内容属性结合的字符串。这意味着这个可以用:

  1. div::before {
  2. content: attr(data-color);
  3. }

提到这一点,是因为可能很想用这种方式拉出一个数字来用于计算,比如:

  1. <div class="grid" data-columns="7" data-gap="2">...</div>
  1. .grid {
  2. display: grid;
  3. /* Neither of these work */
  4. grid-template-columns: repeat(attr(data-columns), 1fr);
  5. grid-gap: calc(1rem * attr(data-gap));
  6. }

幸运的是,这并不重要,因为在HTML中的自定义属性同样有用,甚至更有用!

  1. <div class="grid" style="--columns: 7; --gap: 2rem;">...</div>
  1. .grid {
  2. display: grid;
  3. /* Yep! */
  4. grid-template-columns: repeat(var(--columns), 1fr);
  5. grid-gap: calc(var(--gap));
  6. }

浏览器工具

浏览器DevTools将展示在样式表中编写的calc()
Firefox DevTools – Rules
如果需要找出计算值,有一个计算选项卡(在所有浏览器的DevTools中)会显示它。
Chrome DevTools – Computed

浏览器支持

这个浏览器支持数据来自Caniuse,它有更详细的数据。数字表示浏览器支持该版本及以上的功能。
CSS 动态计算,calc使用指南 - 图1
如果真的需要支持超远期(如IE 8或Firefox 3.6),通常的技巧是在使用calc()的属性或值之前再添加一个属性或值。

  1. .el {
  2. width: 92%; /* Fallback */
  3. width: calc(100% - 2rem);
  4. }

calc()也有不少已知的问题,但都是针对旧浏览器的。canIuse……列出了13个,下面是一小部分。

  • Firefox <59 不支持color函数的calc()。例如:color: hsl(calc(60 * 2), 100%, 50%)
  • calc()用于任何一个值时,IE 9 - 11将不会渲染盒影属性。
  • IE 9 - 11和Edge都不支持表格单元格上的width: calc()

    用例方

    在日常工作中使用。

  • 用它创建了一个.full-bleed 实用工具类:.full-bleed{width:100vw;margin left:calc(50%-50vw);}

  • 用它来为粘性页脚腾出空间。
  • 用它来设置一些流体字体/动态排版……根据最小值、最大值和视口单位的变化率来计算字体大小。不仅仅是字体大小,还有行高。
  • 如果使用calc()作为流体字体情况的一部分,涉及到视口单位等,确保包含一个使用rem或em的单位,这样用户仍然有一些控制权,通过放大或缩小他们需要的字体。
  • “内容宽度 “的自定义属性,然后用它来创建间距,比如 margins: .margin { width: calc( (100vw - var(--content-width)) / 2); }
  • 用它创建了一个跨浏览器的drop-cap组件。下面是它的一部分。.drop-cap { --drop-cap-lines: 3; font-size: calc(1em * var(-drop-cap-lines) * var(-body-line-height)); }
  • 用它来使文章页面上的一些图片溢出其容器。
  • 用它与padding和vw/vh单位相结合,在页面上正确地放置了一个可视化。
  • 用它来克服背景-位置的限制,但特别是在渐变中定位颜色停止的限制。比如 “在底部少停0.75em”。

    其他技巧

  1. 一个两层网格,可以分成一列,而无需媒体查询
  2. 一个纵横比的头部组件
  3. 增强高对比度的颜色
  4. 帮助解决基于百分比的剪辑路径的坐标问题