CSS 中的 ∞(无穷)你知道多少

众所周知,很多编程语言都提供了两个特殊的数值,用以表示无穷大和无穷小。例如,在JavaScript中,我们使用 Infinity 关键词表示无穷大(∞),而 -Infinity 表示无穷小(-∞)。然而,或许你会感到意外的是,CSS 中同样存在这两个概念------无穷大(infinity)和无穷小(-infinity)。当我第一次得知这个信息时,确实让我感到十分惊奇。这个发现使我觉得 CSS 变得更加神奇有趣。

那么,今天我就和大家一起来聊聊 CSS 的这个常量无穷大(小)!

故事从这里开始...

使用 CSS 给元素定义样式时,有很多场景都会用到一个极大的值。例如:

  • 定义元素在 z 轴的层级,例如给一个模态框会设置 z-index: 99999999 ,这样做,总是希望模态框在 z 轴的层最高,不会被其他元素遮盖

  • 制作一个胶囊按钮时,我习惯性会给 border-radius 设置一个 999999rem

  • 有时为了隐藏一个元素,我也习惯性会给一个绝对定位元素的 left 设置一个 -9999999rem

  • 有时为了隐藏文本内容,我也习惯性会给元素的 text-indent 设置一个 999999rem

  • 等等....

前段时间,有一位网友提到一个相似的问题,定位元素 top 属性的最大值是多少?能否给它设置一个无穷大的值?刚开始,我认为像 99999999vw 这样的值足够了。但后来一想,这样似乎不够严谨。

为了获得一个更严谨的答案,我发现 W3C 的 CSS 的值和单位 (CSS Values and Units Module Level 4)规范中为 CSS 定义了无穷大(infinity)和无穷小(-infinity):

而且还得到了主流浏览器的支持

既然如此,我们就一起来聊聊它,权当是 CSS 的一个新特性

图解 CSS:CSS 的值和单位》一文中与大家探讨了 CSS 中的值和单位,但遗漏了 CSS 的无穷大和无穷小这个常量值。另外,如果你对 CSS 新特性感兴趣的话,请移步阅读我撰写的《现代 CSS》!

CSS 中无穷的定义

W3C 规范是这样描述 CSS 中的无穷

When a calculation or a subtree of a calculation becomes infinite or NaN, representing it with a numeric value is no longer possible. To aid in serialization of these degenerate values, the following additional math constants are defined:

简而言之,当计算的值无法用数字表示时,它可能变为无穷大、无穷小或 NaN。为了序列化这些退化值,W3C 的 CSS 工作小组定义了以下额外的数学常量:

  • infinity(无穷大): 表示正无穷大的值(+∞),它表示最大可能的值

  • -infinity(无穷小): 表示负无穷小的值(−∞),它表示最小可能的值,与 calc(-1 * infinity) 的结果等同

  • NaN(非数值): 表示无定义值

需要注意的是,所有这些关键词都属于 <number> 类型的值。

与一般 CSS 关键词规则相同,这些关键词不区分大小写。因此,calc(InFiNiTy) 是合法的,但 NaN 必须使用规范规定的大小写进行序列化。接下来我们将重点讨论 CSS 中的无穷,而不涉及 NaN

在深入讨论之前,有一个重要的基本规则:无穷大( infinity )和无穷小( -infinity )只能在 calc() 函数中使用 。此外,它们是 <number> 值类型,因此要获得无穷大的长度值,需要使用类似 calc(infinity * 1px) 的表达式。

CSS 中哪些计算值会是无穷大

正如前文所述,CSS 中的正无穷大(+∞)和负无穷小(−∞)可以直接使用数学常量 infinity-infinity 编写。此外,CSS 还可以通过其他方式获得这些特殊值,例如某些计算的结果可能会产生无穷大或无穷小。

  • 将一个值除以 0 将会产生 +∞−∞,具体取决于标准符号规则。例如, calc(1 ``/ 0``) 将会产生 +∞ calc(-1 ``/ 0``) 将会产生 −∞。这也适用于单位。因此,你可以使用 calc(1px / 0) 得到与 calc(infinity * 1px) 相同的值。

  • ±∞ 添加或减去任何值都会产生相应的无穷大,例如 calc(infinity + 1px)

  • 将任何值乘以 ±∞ 都会产生相应的无穷大,例如 calc(infinity * 1px)

  • 将任何值除以 ±∞ 会产生零

  • 数学函数中的某些参数组合被定义为产生无穷大,例如,pow(0, -1) 产生 +∞

注意:下面关于产生 NaN 的规则覆盖了上述产生无穷大的规则。

NaN(非数值)是某些没有明确定义值的操作的结果。它可以直接使用数学常量 NaN 编写,也可以作为某些计算的结果产生:

  • 将零除以零,将 ±∞ 除以 ±∞,将 0 乘以 ±∞,将 +∞ 添加到 −∞,或者减去相同符号的两个无穷大会产生 NaN

  • 如果有冲突,这些规则会覆盖任何其他结果。例如,0 / 0NaN,而不是 +∞

  • 数学函数中的某些参数组合被定义为产生 NaN ,例如,在 sin(θ)cos(θ)tan(θ) 函数中,如果 θinfinity-infinityNaN ,则返回的结果为 NaN ;在 asin(θ)acos(θ) 函数中,如果 θ 小于 -1 或大于 1 ,则返回的结果为 NaN

  • 任何具有至少一个 NaN 参数的操作都会产生 NaN

  • NaN 不会逃脱顶级计算;它会被审查为零值。

例如,calc(-5 * 0) 产生一个无符号零,即计算解析为 0⁻,但由于它是一个顶级计算,因此它会被审查为无符号零。另一方面,calc(1 / calc(-5 * 0)) 产生 −∞,与 calc(1 / (-5 * 0)) 相同,即内部计算解析为 0⁻,因为它不是顶级计算,所以它保持不变传递到外部 calc 以产生 −∞。如果它被审查为无符号零,它将产生 +∞

虽然有这么多种方式可以在 CSS 中获得无穷大或无穷小,但我仍然建议直接使用 infinity-infinity ,这样能更清晰地表达意图,而不是在样式表中放入一个巨大的数字(或数字值),以使 CSS 更加清晰。

CSS 中无穷大的用例

CSS 无穷大(infinity)最典型的用例应该是 z-index 。如果将 z-index 的值设置为 infinity ,你将获取最大可能值,再也不用担心别人设置值胜过你。例如下面这个示例,无论你在蓝色的卡片上设置多大的 z-index 值,它都无法用于设置了 z-index: calc(infinity) 的紫色卡片:

HTML 复制代码
<div class="blue"></div>

<div class="purple"></div>
CSS 复制代码
.blue {
    --z-index: 999999999;
    z-index: var(--z-index);
}

.purple {
    z-index: calc(infinity); 
}

无论你在输入框中输入多少个 9 ,最终无法胜过紫色卡片在 z 轴上的层级:

Demo 地址:codepen.io/airen/full/...

你可曾知道,z-index 的上限值是 2147483647 。那么当 z-index 属性值 infinity 遇到其上限值 2147483647 时,又谁将胜出呢?这是一个意思的问题。

通常情况之下,z-index 属性的 infinity2147483647 值被视为相同的值,但无法确定 calc(infinity) 计算的值就等于 2147483647 。我们写个测试案例来验证它。

了解 CSS 的 z-index 属性的开发者应该知道。 要比较 z-index ,那么它们必须位于相同的堆叠环境中。例如下面这个示例,应用 z-index 的两个元素不在同一个堆叠环境中,即使将 z-index 属性的值设置为 infinity 它也无法胜出:

HTML 复制代码
<div class="parent">
    z-index: 2;
    <div class="blue">z-index: infinity</div>
</div>

<div class="purple">z-index: 3</div>
CSS 复制代码
.parent {
    z-index: 2;

    .blue {
        z-index: calc(infinity);
    }
}

.purple {
    z-index: 3; 
}

Demo 地址:codepen.io/airen/full/...

上面示例这种情景,很多初学者都认为 z-index 失效了,其实这是它的基本规则。有关于这方面更详细的介绍,可以移步阅读《防御式 CSS 精讲》中的《z-index 的失效与修复》一文。

防御式 CSS 精讲》:如何使自己构建的 UI 或编写的 CSS 代码更具防御性(健壮性),确保还原的 UI 在不同的条件下都能工作,不打破 Web 布局或 Web UI,是每个专业的 Web 前端开发者必备的技能。这本小册从"防御式"角度出发,分析了布局、UI 效果、媒体对象、交互体验等多种场景下编写 CSS 的注意事项,你可以把它当作 CSS 技巧集合或 CSS 魔法集合!

继续回到 z-index 身上。z-index 还有另一个规则:"在相同的堆叠环境当中,如果两元素指定的 z-index 相同,那么将根据元素在 HTML 源码中出现的顺序来决定,即先出现的先胜。换句话说,如果我们的假设是成功的(infinity2147483647 大),则无论 HTML 的顺序如何,它都应该位于顶部。

因此,我准备了两个堆栈上下文,如下所示,每个堆栈上下文都有不同的 HTML 顺序:

HTML 复制代码
<div class="wrapper">
    <div class="blue">z-index: 2147483647</div>
    <div class="purple">z-index: calc(infinity)</div>
</div>

<div class="wrapper">
    <div class="purple">z-index: calc(infinity)</div>
    <div class="blue">z-index: 2147483647</div>
</div>
CSS 复制代码
.wrapper {
    z-index: 0;
}

.blue {
    z-index: 2147483647;
}

.purple {
    z-index: calc(infinity);
}

.blue.purple 放在相同的堆叠上下文环境中进行比较。如果 z-indexinfinity2147483647 相同,则按照 HTML 的顺序来决定谁胜出;如果是 infinity 大于 2147483647 ,则设置了 calc(infinity) 元素(.purple)始终将位于 z 轴的顶部。

Demo 地址:codepen.io/airen/full/...

结果已经告诉我们答案了,z-index 的值 infinity2147483647 相等。但这并不能说 infinity 就等于 2147483647 。听起来有点拗口,甚至晕乎。你可以尝试一下,calc(infinity - 2147483647) 结果和 2147483647 也一样。这就意味着它已经停止在 z-index 限值处。

这个案例从侧面又说明,calc(infinity) 不一定始终能让 z-index 胜出,只不过它的上限值 2147483647 并不广为人知,而且也很难记得住这一串数值。也就是说,当有一天,你将 z-index 属性的值设置为 calc(infinity) ,它并没有胜出的话,你需要知道其中的原委,并做出正确的选择。

另一个典型用例就是胶囊按钮或类似于胶囊外形的 UI:

以按钮为例,我习惯性会将 border-radius 设置为 999rem999vmax

CSS 复制代码
.pill {
    border-radius: 999vmax;
}

这样做不管元素高度是多少,都可以实现胶囊 UI 的效果:

Demo 地址:codepen.io/airen/full/...

你现在可以将 999vmax 替换成 calc(1px * infinity)

CSS 复制代码
.pill {
    border-radius: calc(1px * infinity);
}

Demo 地址:codepen.io/airen/full/...

这意味着我们不需要知道元素(矩形框)的尺寸,它也能正常的工作。不过,在某些边缘情况上,会遇到一些奇怪的行为。要是你想进一步了解 border-radius 边缘情况会发生什么,可以移步阅读《你不知道的 border-radius》。

另一个相似的用途可以用于隐藏文本或隐藏元素中。例如在视觉上隐藏元素(仅供屏幕阅读器可见)的代码片段:

CSS 复制代码
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
    left: -9999vmax;
}

代码中的 left: -9999vmax 可以使用 calc(-1px * infinity) 来替代:

CSS 复制代码
.sr-only {
    /* ... */
    left: calc(-1px * infinity);
}

从功能上说,没有啥区别。最终效果是相同的。但我认为它确实有助于使代码更具可读性,因为无穷大传达了真正的意图。

最后再来看一个与动画相关的案例。假设你有一个元素要从屏幕上移出,往往我们会像下面这样来定义动画:

CSS 复制代码
@keyframes slideOutRight {
    to {
        translate: 100vw;
    }
}

.ani {
    animation: slideOutRight 1s ease-out infinite;
}

Demo 地址:codepen.io/airen/full/...

要是你想着将 @keyframes 中的 translate 属性值替换为 calc(1px * infinity) ,你会发现元素将立即跳到动画的最后并保持在那里:

Demo 地址:codepen.io/airen/full/...

丝毫感觉不到动画的效果。这是有道理的。在通往无穷大的路上没有增量值。无穷大的一部分仍然是无穷大。因此,在动画的每一帧中,动画值都是无穷大。

在《Web 动画之旅》的《提升可访问性动画的关键技巧》一文中,着重提到过,我们创建的一些 Web 动画效果对于部分群体是不好的。为了不给这些用户添加不必要的麻烦,我们需要添加下面这样的代码,减少页面上的运动:

CSS 复制代码
@media (prefers-reduced-motion: reduce) {
    *,
    ::before,
    ::after {
        animation-delay: -1ms !important;
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        background-attachment: initial !important;
        scroll-behavior: auto !important;
        transition-duration: 0s !important;
        transition-delay: 0s !important;
    }
}

有了无穷大这个常量之后,你又可以为减少运动添加一个更简单的方法。你只需要将 transition-delayanimation-delay 的值设置无穷大,那么页面上的动画就永远不会开始播放:

CSS 复制代码
@media (prefers-reduced-motion: reduce) {
    *,
    ::before,
    ::after {
        animation-delay: calc(1s * infinity) !important;
        transition-delay: calc(1s * infinity) !important;
    }
}

如果你对 Web 动画感兴趣的话,请移步阅读《Web 动画之旅》:

猛击这里直达:s.juejin.cn/ds/iFrsMxYy...

小结

简单地的小结一下,CSS 中的无穷大,主要要知道的是它本质上是表示特定情况下最大可能值或最小可能值的简写。在一些特定的情景中,它们非常有用,既能帮助我们实现想要的效果,又传达了 CSS 真正的意图。

到目前为止,该属性值已得到了很好的支持,但是否在项目中使用它,这取决于你。最后,我想再强调一次的是,无穷大可以作为一种表达意图的良好指示,使你的 CSS 更易读,但这当然不是强制性的。


如果你觉得该教程对你有所帮助,请给我点个赞。要是你喜欢 CSS ,或者想进一步了解和掌握 CSS 相关的知识,请关注我的专栏,或者移步阅读下面这些系列教程:

相关推荐
雯0609~9 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ12 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z17 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜41 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40442 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish43 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five44 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序44 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫54144 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
酷酷的威朗普1 小时前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5