乐趣区

关于前端:现代-CSS-解决方案CSS-数学函数

在 CSS 中,其实存在各种各样的函数。具体分为:

  • Transform functions
  • Math functions
  • Filter functions
  • Color functions
  • Image functions
  • Counter functions
  • Font functions
  • Shape functions
  • Reference functions
  • CSS grid functions

本文,将具体介绍其中的 CSS 数学函数(Math functions)中,曾经被浏览器大规模反对的 4 个:

  • calc()
  • min()
  • max()
  • clamp()

为什么说是被浏览器大规模反对的?因为除了这 4 个目前曾经失去大规模反对的数学函数外,其实标准 CSS Values and Units Module Level 4 曾经定义了诸如三角函数相干 sin()cos()tan() 等,指数函数相干 pow()sqrt() 等等数学函数,只是目前都处于实验室阶段,还没有浏览器反对它们,须要给工夫一点工夫。

Calc()

calc() 此 CSS 函数容许在申明 CSS 属性值时执行一些计算。

语法相似于

{width: calc(100% - 80px);
}

一些须要留神的点:

  • +- 运算符的两边必须要有空白字符。比方,calc(50% -8px) 会被解析成为一个有效的表达式,必须写成calc(8px + -50%)
  • */ 这两个运算符前后不须要空白字符,但如果思考到统一性,依然举荐加上空白符
  • 用 0 作除数会使 HTML 解析器抛出异样
  • 波及主动布局和固定布局的表格中的表列、表列组、表行、表行组和表单元格的宽度和高度百分比的数学表达式,auto 可视为已指定。
  • calc() 函数反对嵌套,但反对的形式是:把被嵌套的 calc() 函数全当成一般的括号。(所以,函数内间接用括号就好了。)
  • calc() 反对与 CSS 变量混合应用

看一个最常见的例子,页面构造如下:

<div class="g-container">
    <div class="g-content">Content</div>
    <div class="g-footer">Footer</div>
</div>

页面的 g-footer 高为 80px,咱们心愿不论页面多长,g-content 局部都能够占满残余空间,像是这样:

这种布局应用 flex 的弹性布局能够轻松实现,当然,也能够应用 calc() 实现:

.g-container {height: 100vh;}
.g-content {height: calc(100vh - 80px);
}
.g-footer {height: 80px;}

上面列举一些 Calc() 的进阶技巧。

Calc 中的加减法与乘除法的差别

留神,calc() 中的加减法与乘除法的差别:

{font-size: calc(1rem + 10px);
    width: calc(100px + 10%);
}

能够看到,加减法两边的操作数都是须要单位的,而乘除法,须要一个无单位数,仅仅示意一个倍率:

{width: calc(100% / 7);
    animation-delay: calc(1s * 3);
}

Calc 的嵌套

calc() 函数是能够嵌套应用的,像是这样:

{width: calc(100vw - calc(100% - 64px));
}

此时,外部的 calc() 函数能够进化写成一个括号即可 (),所以上述代码等价于:

{width: calc(100vw - (100% - 64px));
}

也就是嵌套内的 calc(),calc 几个函数字符能够省略

Calc 内不同单位的混合运算

calc() 反对不同单位的混合运算,对于长度,只有是属于长度相干的单位都能够进行混合运算,蕴含这些:

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

这里有一个有意思的点,运算必定是耗费性能的,早年间,有这样一段 CSS 代码,能够间接让 Chrome 浏览器解体 Crash:

<div></div>

CSS 款式如下:

div {--initial-level-0: calc(1vh + 1% + 1px + 1em + 1vw + 1cm);

  --level-1: calc(var(--initial-level-0) + var(--initial-level-0));
  --level-2: calc(var(--level-1) + var(--level-1));
  --level-3: calc(var(--level-2) + var(--level-2));
  --level-4: calc(var(--level-3) + var(--level-3));
  --level-5: calc(var(--level-4) + var(--level-4));
  --level-6: calc(var(--level-5) + var(--level-5));
  --level-7: calc(var(--level-6) + var(--level-6));
  --level-8: calc(var(--level-7) + var(--level-7));
  --level-9: calc(var(--level-8) + var(--level-8));
  --level-10: calc(var(--level-9) + var(--level-9));
  --level-11: calc(var(--level-10) + var(--level-10));
  --level-12: calc(var(--level-11) + var(--level-11));
  --level-13: calc(var(--level-12) + var(--level-12));
  --level-14: calc(var(--level-13) + var(--level-13));
  --level-15: calc(var(--level-14) + var(--level-14));
  --level-16: calc(var(--level-15) + var(--level-15));
  --level-17: calc(var(--level-16) + var(--level-16));
  --level-18: calc(var(--level-17) + var(--level-17));
  --level-19: calc(var(--level-18) + var(--level-18));
  --level-20: calc(var(--level-19) + var(--level-19));
  --level-21: calc(var(--level-20) + var(--level-20));
  --level-22: calc(var(--level-21) + var(--level-21));
  --level-23: calc(var(--level-22) + var(--level-22));
  --level-24: calc(var(--level-23) + var(--level-23));
  --level-25: calc(var(--level-24) + var(--level-24));
  --level-26: calc(var(--level-25) + var(--level-25));
  --level-27: calc(var(--level-26) + var(--level-26));
  --level-28: calc(var(--level-27) + var(--level-27));
  --level-29: calc(var(--level-28) + var(--level-28));
  --level-30: calc(var(--level-29) + var(--level-29));

  --level-final: calc(var(--level-30) + 1px);

    border-width: var(--level-final);                                 
    border-style: solid;
}

能够看到,从 --level-1 --level-30,每次的运算量都是成倍的增长,最终到 --level-final 变量,开展将有 2^30 = 1073741824--initial-level-0 表达式的内容。

并且,每个 --initial-level-0 表达式的内容 — calc(1vh + 1% + 1px + 1em + 1vw + 1cm),在浏览器解析的时候,也曾经足够简单。

混合在一起,就导致了浏览器的 BOOM(Chrome 70 之前的版本),为了能看到成果,咱们将上述款式赋给某个元素被 hover 的时候,失去如下成果:

当然,这个 BUG 目前曾经被修复了,咱们也能够通过这个小 DEMO 理解到,一是 calc 是能够进行不同单位的混合运算的,另外一个就是留神具体应用的时候如果计算量微小,可能会导致性能上较大的耗费。

当然,不要 将长度单位和非长度单位混合应用,像是这样:

{animation-delay: calc(1s + 1px);
}

Calc 搭配 CSS 自定义变量应用

calc() 函数十分重要的一个个性就是可能搭配 CSS 自定义以及 CSS @property 变量一起应用。

最简略的一个 DEMO:

:root {--width: 10px;}
div {width: calc(var(--width));
}

当然,这样看上去,基本看不出这样的写法的作用,如同没有什么意义。理论利用场景中,会比上述的 DEMO 要略微简单一些。

假如咱们要实现这样一个 loading 动画成果,一开始只有 3 个球:

可能的写法是这样,咱们给 3 个球都增加同一个旋转动画,而后别离管制他们的 animation-delay

<div class="g-container">
    <div class="g-item"></div>
    <div class="g-item"></div>
    <div class="g-item"></div>
</div>
.item:nth-child(1) {animation: rotate 3s infinite linear;}
.item:nth-child(2) {animation: rotate 3s infinite -1s linear;}
.item:nth-child(3) {animation: rotate 3s infinite -2s linear;}

如果有一天,这个动画须要扩大成 5 个球的话,像是这样:

咱们就不得已,得去既增加 HTML,又批改 CSS。而如果借助 Calc 和 CSS 变量,这个场景就能够略微简化一下。

假如只有 3 个球:

<div class="g-container">
    <div class="g-item" style="--delay: 0"></div>
    <div class="g-item" style="--delay: 1"></div>
    <div class="g-item" style="--delay: 2"></div>
</div>

咱们通过 HTML 的 Style 标签,传入 --delay 变量,在 CSS 中间接应用它们:

.g-item {
    animation: rotate 3s infinite linear;
    animation-delay: calc(var(--delay) * -1s);
}
@keyframes rotate {
    to {transform: rotate(360deg);
    }
}

而当动画批改成 5 个球时,咱们就不须要批改 CSS,间接批改 HTML 即可,像是这样:

<div class="g-container">
    <div class="g-item" style="--delay: 0"></div>
    <div class="g-item" style="--delay: 0.6"></div>
    <div class="g-item" style="--delay: 1.2"></div>
    <div class="g-item" style="--delay: 1.8"></div>
    <div class="g-item" style="--delay: 2.4"></div>
</div>

外围的 CSS 还是这一句,不须要做任何批改:

{animation-delay: calc(var(--delay) * -1s);
}

残缺的 DEMO,你能够戳这里:CodePen Demo — Calc & CSS Variable Demo

calc 搭配自定义变量时候的默认值

还是上述的 Loading 动画成果,如果我的 HTML 标签中,有一个标签遗记填充 --delay 的值了,那会产生什么?

像是这样:

<div class="g-container">
    <div class="g-item" style="--delay: 0"></div>
    <div class="g-item" style="--delay: 0.6"></div>
    <div class="g-item"></div>
    <div class="g-item" style="--delay: 1.8"></div>
    <div class="g-item" style="--delay: 2.4"></div>
</div>
{animation-delay: calc(var(--delay) * -1s);
}

因为 HTML 标签没有传入 --delay 的值,并且在 CSS 中向上查找也没找到对应的值,此时,animation-delay: calc(var(--delay) * -1s) 这一句其实是有效的,相当于 animation-delay: 0,成果也就是少了个球的成果:

所以,基于这种状况,能够利用 CSS 自定义变量 var() 的 fallback 机制:

{// (--delay, 1) 中的 1 是个容错机制
    animation-delay: calc(var(--delay, 1) * -1s);
}

此时,如果没有读取到任何 --delay 值,就会应用默认的 1 与 -1s 进行运算。

Calc 字符串拼接

很多人在应用 CSS 的时候,会尝试字符串的拼接,像是这样:

<div style="--url:'bsBD1I.png'"></div>
:root {
    --urlA: 'url(https://s1.ax1x.com/2022/03/07/';
    --urlB: ')';
}
div {
    width: 400px;
    height: 400px;
    background-image: calc(var(--urlA) + var(--url) + var(--urlB));
}

这里想利用 calc(var(--urlA) + var(--url) + var(--urlB)) 拼出残缺的在 background-image 中可应用的 URL url(https://s1.ax1x.com/2022/03/07/bsBD1I.png)

然而,这是不被容许的(无奈实现的)。calc 的没有字符串拼接的能力

惟一可能实现字符串拼接的是在元素的伪元素的 content 属性中。然而也不是利用 calc。

来看这样一个例子,这是 谬误 的:

:root {
    --stringA: '123';
    --stringB: '456';
    --stringC: '789';
}

div::before {content: calc(var(--stringA) + var(--stringB) + var(--stringC));
}

此时,不须要 calc,间接应用自定义变量相加即可。

因而,正确 的写法:

:root {
    --stringA: '123';
    --stringB: '456';
    --stringC: '789';
}
div::before {content: var(--stringA) + var(--stringB) + var(--stringC);
}

此时,内容能够失常展现:

再强调一下,calc 的没有字符串拼接的能力,如下的应用形式都是无奈被辨认的谬误语法:

.el::before {
  // 不反对字符串拼接
  content: calc("My" + "counter");
}
.el::before {
  // 更不反对字符串乘法
  content: calc("String Repeat 3 times" * 3);
}

min()、max()、clamp()

min()、max()、clamp() 适宜放在一起讲。它们的作用彼此之间有所关联。

  • max():从一个逗号分隔的表达式列表中抉择最大(正方向)的值作为属性的值
  • min():从一个逗号分隔的表达式列表中抉择最小的值作为属性的值
  • clamp():把一个值限度在一个下限和上限之间,当这个值超过最小值和最大值的范畴时,在最小值和最大值之间抉择一个值应用

因为在事实中,有十分多元素的的属性不是变化无穷的,而是会依据上下文、环境的变动而变动。

譬如这样一个布局:

<div class="container"></div>
.container {
    height: 100px;
    background: #000;
}

成果如下,.container 块它会随着屏幕的增大而增大,始终占据整个屏幕:

对于一个响应式的我的项目,咱们必定不心愿它的宽度会始终变大,而是当达到肯定的阈值时,宽度从绝对单位变成了相对单位,这种状况就实用于 min(),简略革新下代码:

.container {width: min(100%, 500px);
    height: 100px;
    background: #000;
}

容器的宽度值会在 width: 100%width: 500px 之间做抉择,选取绝对小的那个。

在屏幕宽度有余 500px 时候,也就体现为 width: 100%,反之,则体现为 width: 500px

同理,在相似的场景,咱们也能够应用 max() 从多个值中,选取绝对更大的值。

min()、max() 反对多个值的列表

min()、max() 反对多个值的列表,譬如 width: max(1px, 2px, 3px, 50px)

当然,对于上述表白:

width: max(1px, 2px, 3px, 50px) 其实等于 width: 50px。因而,对于 min()、max() 的具体应用而言,最多应该只蕴含一个具体的相对单位。否则,这样的像上述这种代码,尽管语法反对,然而任何状况下,计算值都是确定的,其实没有意义。

配合 calc

min()、max()、clamp() 都能够配合 calc 一起应用。

譬如:

div {width: max(50vw, calc(300px + 10%));
}

在这种状况下,calc 和相应包裹的括号能够省略,因而,上述代码又能够写成:

div {width: max(50vw, 300px + 10%);
}

基于 max、min 模仿 clamp

当初,有这样一种场景,如果,咱们又须要限度最大值,也须要限度最小值,怎么办呢?

像是这样一个场景,** 字体的大小,最小是 12px,随着屏幕的变大,逐步变大,然而为了防止老人机现象(随着屏幕变大,无限度变大),咱们还须要限度一个最大值 20px。

咱们能够利用 vw 来实现给字体赋动静值,假如在挪动端,设施宽度的 CSS 像素为 320px 时,页面的字体宽度最小为 12px,换算成 vw 即是 320 / 100 = 3.2,也就是 1vw 在 屏幕宽度为 320px 时候,体现为 3.2px,12px 约等于 3.75 vw。

同时,咱们须要限度最大字体值为 20px,对应的 CSS 如下:

p {font-size: max(12px, min(3.75vw, 20px));
}

看看成果:

通过 max()min() 的配合应用,以及搭配一个绝对单位 vw,咱们胜利的给字体设置了上上限,而在这个上上限之间实现了动态变化。

当然,下面外围的这一段 max(12px, min(3.75vw, 20px)) 看上去有点绕,因而,CSS 推出了 clamp() 简化这个语法,上面两个写法是等价的:

p {font-size: max(12px, min(3.75vw, 20px));
    // 等价于
    font-size: clamp(12px, 3.75vw, 20px);
}

clamp()

clamp() 函数的作用是把一个值限度在一个下限和上限之间,当这个值超过最小值和最大值的范畴时,在最小值和最大值之间抉择一个值应用。它接管三个参数:最小值、首选值、最大值。

有意思的是,clamp(MIN, VAL, MAX) 其实就是示意 max(MIN, min(VAL, MAX))

应用 vw 配合 clamp 实现响应式布局

咱们持续下面的话题。

在不久的过来,挪动端的适配方面,应用更多的 rem 适配计划,可能会借助一些现成的库,相似于 flexible.js、hotcss.js 等库。rem 计划比拟大的一个问题在于须要一段 JavaScript 响应视口变动,重设根元素的 font-size,并且,应用 rem 多少有点 hack 的感觉。

在当初,在挪动端适配,咱们更为推崇的是 vw 纯 CSS 计划,与 rem 计划相似,它的实质也是页面的等比例缩放。它的一个问题在于,如果仅仅应用 vw,随着屏幕的一直变大或者放大,内容元素将会始终变大变小上来,这也导致了在大屏幕下,许多元素看着切实太大了!

因而,咱们须要一种可能管制最大、最小阈值的形式,像是这样:

此时,clamp 就能十分好的派上用场,还是咱们上述的例子,这一段代码 font-size: clamp(12px, 3.75vw, 20px),就能将字体限度在 12px - 20px 的范畴内。

因而,对于挪动端页面而言,所有波及长度的单位,咱们都能够应用 vw 进行设置。而诸如字体、内外边距、宽度等不应该齐全等比例缩放的,采纳 clamp() 管制最大最小阈值

在 Modern Fluid Typography Using CSS Clamp 一文中,对应用 clamp() 进行流式响应式布局还有更为深刻的探讨,感兴趣的能够深刻浏览。

总结一下,对于挪动端页面,咱们能够以 vw 配合 clamp() 的形式,​实现整个挪动端布局的适配。它的劣势在于:

  • 没有额定 JavaScript 代码的引入,纯 CSS 解决方案
  • 可能很好地管制边界阈值,正当的进行缩放展现

反向响应式变动

还有一个技巧,利用 clamp() 配合负值,咱们也能够反向操作,失去一种屏幕越大,字体越小的反向响应式成果:

p {font-size: clamp(20px, -5vw + 96px, 60px);
}

看看成果:

这个技巧挺有意思的,因为 -5vw + 96px 的计算值会随着屏幕的变小而增大,实现了一种反向的字体响应式变动。

总结

总结一下,正当使用 min()、max()、clamp(),是构建古代响应式布局的重点,咱们能够辞别传统的须要 JavaScript 辅助的一些计划,基于 CSS 这些数学函数即可实现所有的诉求。

一些进阶浏览十分好的文章:

  • A guide to the min(), max(), and clamp() CSS functions
  • Modern Fluid Typography Using CSS Clamp

最初

好了,本文到此结束,心愿本文对你有所帮忙 :)

想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 — iCSS 前端趣闻 😄

更多精彩 CSS 技术文章汇总在我的 Github — iCSS,继续更新,欢送点个 star 订阅珍藏。

如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。

退出移动版