为高度添加动画效果:auto;(以及其他固有尺寸关键字)

使用 interpolate-size 属性或 calc-size() 函数可实现从长度到内在尺寸关键字的流畅过渡和动画效果,反之亦然。

发布时间:2024 年 9 月 17 日

简介

一项经常被请求的 CSS 功能是能够以 height: auto 的形式进行动画处理。该请求的细微变化是转换 width 属性(而非 height),或者转换为 min-contentmax-contentfit-content 等关键字表示的任何其他固有尺寸

例如,在以下演示中,如果标签在用户悬停在图标上时能够以流畅的动画效果调整为自然宽度,将会非常棒。

使用的 CSS 如下所示:

nav a {
    width: 80px;
    overflow-x: clip;
    transition: width 0.35s ease; /* 👈 Transition the width */

    &:hover,
    &:focus-visible {
        width: max-content; /* 👈 Doesn't work with transitions */
    }
}

即使声明了 transition 来转换 width 属性,并在 :hover 上声明了 width: auto,也不会发生流畅的转换。而是突然发生变化。

使用 interpolate-size 在内在尺寸关键字之间进行动画过渡

Browser Support

  • Chrome: 129.
  • Edge: 129.
  • Firefox: not supported.
  • Safari: not supported.

Source

借助 CSS interpolate-size 属性,您可以控制是否应允许 CSS 内在尺寸关键字的动画和过渡。

其默认值为 numeric-only,表示不启用插值。将该属性设置为 allow-keywords 时,您选择在浏览器可以为这些关键字添加动画效果的情况下,将长度插值为 CSS 内在尺寸关键字。

按规范

  • numeric-only:无法插值 <intrinsic-size-keyword>
  • allow-keywords:如果其中一个值为 <intrinsic-size-keyword>,另一个值为 <length-percentage>,则可以对这两个值进行插值。[…]

由于 interpolate-size 属性是可继承的,因此您可以在 :root 上声明该属性,以便为整个文档启用从内在尺寸关键字转换到内在尺寸关键字的功能。这是推荐的方法

/* Opt-in the whole page to interpolate sizes to/from keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

在以下演示中,此规则已添加到代码中。因此,从 width: autowidth: auto 的动画会正常运行(在支持的浏览器中):

通过缩小选择器来限制“选择接受”的覆盖面

如果您想将 allow-keywords 选择接受功能仅限于文档的子树,请将选择器从 :root 调整为仅选择要定位的元素。例如,如果网页的 <header> 与此类转场效果不兼容,您可以将选择启用此功能的范围限制为仅限 <main> 元素及其子元素,如下所示:

main { /* 👈 Scope the opt-in to only <main> and its descendants */
    interpolate-size: allow-keywords;
}

为什么默认不允许大小调整关键字的动画效果?

关于此“用户选择启用”机制的常见反馈是,浏览器应默认仅允许从内在尺寸关键字到长度的过渡和动画。

在开发此功能期间,我们研究了启用此行为的选项。工作组发现,默认启用此功能不向后兼容,因为许多样式表都假定内在尺寸关键字(例如 automin-content)无法进行动画处理。您可以在关于相关 CSS 工作组问题的此评论中找到详细信息。

因此,此属性是可选的。得益于其继承特性,选择启用整个文档只需在 :root 上声明 interpolate-size: allow-sizes,如前所述。

使用 calc-size() 在内在尺寸关键字之间进行动画过渡

Browser Support

  • Chrome: 129.
  • Edge: 129.
  • Firefox: not supported.
  • Safari: not supported.

Source

如需实现从内在尺寸关键字到其他关键字的插值,另一种方法是使用 calc-size() 函数。它允许以安全且明确的方式对内在尺寸执行数学运算。

该函数接受两个参数,顺序如下:

  • calc-size 依据,可以是 <intrinsic-size-keyword>,也可以是嵌套的 calc-size()
  • calc-size 计算,可让您使用 calc-size 基准进行计算。如需引用 calc-size 基准,请使用 size 关键字。

下面是一些示例:

width: calc-size(auto, size);        // = the auto width, unaltered
width: calc-size(min-content, size); // = the min-content width, unaltered

calc-size() 添加到原始演示中,代码如下所示:

nav a {
    width: 80px;
    overflow-x: clip;
    transition: width 0.35s ease;

    &:hover,
    &:focus-visible {
        width: calc-size(max-content, size); /* 👈 */
    }
}

从视觉上看,结果与使用 interpolate-size 时完全相同。因此,在这种特殊情况下,您应使用 interpolate-size

calc-size() 的优势在于能够执行计算,而 interpolate-size 无法执行此操作:

width: calc-size(auto, size - 10px); // = The auto width minus 10 pixels
width: calc-size(min-content, size + 1rem); // = The min-content width plus 1rem
width: calc-size(max-content, size * .5);   // = Half the max-content width

例如,如果您希望将页面上的所有段落的大小设为最接近 50px 的倍数,可以使用以下代码:

p {
    width: calc-size(fit-content, round(up, size, 50px));
    height: calc-size(auto, round(up, size, 50px));
}

calc-size() 还允许您在两个 calc-size() 之间插值,前提是这两个 calc-size() 的 calc-size 基准相同。这也是 interpolate-size 无法实现的。

#element {
    width: min-content; /* 👈 */
    transition: width 0.35s ease;

    &:hover {
        width: calc-size(min-content, size + 10px); /* 👈 */
    }
}

为什么不允许在 calc() 中使用 <intrinsic-size-keyword>

关于 calc-size(),一个常见的问题是,CSS 工作组为何没有调整 calc() 函数以支持内在尺寸关键字。

原因之一是,您在计算时不得混合使用内在尺寸关键字。例如,您可能会想编写看起来有效的 calc(max-content - min-content),但实际上它并不有效。calc-size() 会强制执行正确性检查,因为与 calc() 不同,它仅接受一个 <intrinsic-size-keyword> 作为第一个参数。

另一个原因是情境感知。某些布局算法针对特定的内在尺寸关键字具有特殊行为。calc-size() 被明确定义为表示内在尺寸,而不是 <length>。因此,这些算法能够将 calc-size(<intrinsic-size-keyword>, …) 视为 <intrinsic-size-keyword>,从而保持对该关键字的特殊行为。

使用哪种方法?

在大多数情况下,请在 :root 上声明 interpolate-size: allow-keywords。这是启用从内在尺寸关键字到其他关键字的动画的最简单方法,因为它本质上就是一行代码。

/* Opt-in the whole page to animating to/from intrinsic sizing keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

这段代码是一项不错的渐进式增强功能,因为不支持它的浏览器会回退为不使用转场效果。

如果您需要对某些操作(例如执行计算)进行更精细的控制,或者想要使用只有 calc-size() 才能执行的行为,可以改用 calc-size()

#specific-element {
    width: 50px;

    &:hover {
        width: calc-size(fit-content, size + 1em); /* 👈 Only calc-size() can do this */
    }
}

不过,如果要在代码中使用 calc-size(),您需要为不支持 calc-size() 的浏览器添加回退。例如,添加额外的尺寸声明,或回退到使用 @supports 进行特征检测。

width: fit-content;
width: calc-size(fit-content, size + 1em);
       /* 👆 Browsers with no calc-size() support will ignore this second declaration,
             and therefore fall back to the one on the line before it. */

更多演示

下面是一些充分利用 interpolate-size: allow-keywords 优势的其他演示。

通知

以下演示是@starting-style 演示的分支。调整了代码,以允许添加高度不同的项。

为此,整个网页都选择启用尺寸关键字插值,并且每个 .item 元素上的 height 都设置为 auto。否则,代码与分叉之前完全相同。

:root {
    interpolate-size: allow-keywords; /* 👈 */
}

.item {
    height: auto; /* 👈 */

    @starting-style {
        height: 0px;
    }
}

<details> 元素添加动画

您希望使用此类插值的典型用例是,为展开式微件或独占折叠式动画化。在 HTML 中,您可以使用 <details> 元素来实现此目的。

借助 interpolate-size: allow-keywords,您可以实现很多功能:

@supports (interpolate-size: allow-keywords) {
    :root {
        interpolate-size: allow-keywords;
    }
    
    details {
        transition: height 0.5s ease;
        height: 2.5rem;
        
        &[open] {
            height: auto;
            overflow: clip; /* Clip off contents while animating */
        }
    }
}

不过,如您所见,动画仅在展开式微件打开时运行。为此,Chrome 正在开发 ::details-content 伪元素,该元素将于今年晚些时候在 Chrome 中发布(我们将在日后的文章中介绍该元素)。结合使用 interpolate-size: allow-keywords::details-content,您可以获得双向动画: