我知道这是 Chris 一直想要的,所以他当天就发表了一篇 很棒的文章,这并不奇怪。事实上,我最初是从他的帖子中了解到这个消息,而且无法找到任何公告。所以,我觉得有必要记下一些笔记,因为它感觉是一个重要的发展。
消息是:现在可以过渡到auto
了!好吧,它将会成为现实。Chrome Canary 最近发布了对它的支持,目前只有在那里才能找到它。即使如此,我们仍然不知道 Chrome Canary 的实现是否会在该功能正式发布时成为语法的一部分。
问题
情况是这样的。您有一个元素。您已经对其进行了标记,放入了内容,并应用了许多样式。您知道它有多高吗?当然不知道!当然,我们可以让 JavaScript 为我们评估该元素,但就 CSS 而言,元素的计算尺寸是未知的。
这使得例如将该元素从height: 0
动画到height: whatever
变得困难。我们需要知道“whatever”是什么,而我们只能通过在元素上设置固定高度来做到这一点。这样,我们就有数字可以从零高度过渡到那个特定高度。
.panel {
height: 0;
transition: height 0.25s ease-in;
&.expanded {
height: 300px;
}
}
但是如果该元素随着时间的推移而发生变化会怎么样?也许字体发生了变化,我们添加了填充,插入了更多内容……任何更改尺寸的事情。我们可能需要将height: 300px
更新为任何最适合的新固定高度。这就是为什么我们经常看到 JavaScript 用于切换尺寸扩展和收缩的东西,以及其他解决方法。
我说这是关于height
属性,但我们也在讨论逻辑等效的block-size
,以及width
和inline-size
。或者任何方向!
auto
过渡到这是目标,对吧?当高度尺寸未知时,我们倾向于使用height: auto
。从那里,我们让 JavaScript 计算它的评估结果,并从那里开始。
当前的 Chrome 实现使用 CSS calc-size()
来完成繁重的工作。它识别auto
关键字,并根据它的名字,计算出那个数字。换句话说,我们可以这样做,而不是使用固定高度方法
.panel {
height: 0;
transition: height 0.25s ease-in;
&.expanded {
height: calc-size(auto);
}
}
就是这样!当然,calc-size()
能够处理更复杂的表达式,但事实上我们只需要向它提供一个关于元素高度的模糊关键字就令人印象深刻。这使我们能够从固定值过渡到元素的内在大小,然后再返回。
我不得不试一试。我确信这里有很多用例,但我使用了一个日历组件中的浮动按钮,它表示待定的日历邀请数量。单击该按钮,一个面板会在日历上方展开,并显示邀请。再次单击它,面板会返回到它来的地方。JavaScript 处理单击交互,触发一个类更改,该更改在 CSS 中过渡高度。
如果您不想打开 Canary,可以观看一个视频
这是相关的 CSS
.invite-panel {
height: 0;
overflow-y: clip;
transition: height 0.25s ease-in;
}
单击时,JavaScript 会在元素上设置自动高度作为内联样式,以覆盖 CSS
<div class="invite-panel" style="height: calc(auto)">
CSS 中的transition
属性让浏览器知道我们打算在某个时候更改height
属性,并使其变得平滑。并且,与任何过渡或动画一样,最好通过 使用prefers-reduced-motion
减慢或删除运动来考虑运动敏感度。
display: none
怎么样?
这是我读到 Chris 的帖子后第一个想到的问题,他也在文章中提到了这一点。从一个元素从display: none
过渡到它的内在大小,有点像从height: 0
过渡。看起来非显示元素的高度为零,但实际上它的计算高度为auto
,除非在它上面声明了特定高度。

因此,如果我们想从 CSS 中的display: none
进行过渡,还需要做额外的工作。我将直接插入 Chris 分享的代码,因为它很好地展示了关键部分
.element {
/* hard mode!! */
display: none;
transition: height 0.2s ease-in-out;
transition-behavior: allow-discrete;
height: 0;
@starting-style {
height: 0;
}
&.open {
height: calc(auto);
}
}
- 元素一开始既有
display: none
又有height: 0
。 - 有一个
.open
类,它将元素的高度设置为calc-size(auto)
。
这是我们要连接的两个点,我们首先在元素上设置transition-behavior: allow-discrete
。这对我来说是新的,但 规范指出transition-behavior
“指定是否会为离散属性启动过渡”。当我们声明allow-discrete
时,“过渡将为离散属性以及可插值属性启动”。
好吧,DevTools 就在那里向我们展示了height: auto
是一个离散属性!但是,请注意@starting-style
声明。如果您不熟悉它,那么您并不孤单。这个想法是,它允许我们为过渡设置一个“开始”样式。由于我们元素的离散高度是auto
,我们需要告诉过渡从height: 0
开始,而不是auto
.element {
/* etc. */
@starting-style {
height: 0;
}
}
现在,我们可以从零过渡到auto
,因为我们有点用@starting-style
覆盖了离散高度。能够做到这一点非常酷!
太棒了。我无数次希望 auto 能够成为一个可行的动画目标。
几个月前我使用 @starting-style 来为原生
<dialog>
的显示制作动画(没什么特别的,只是不透明度和翻译),它一直导致 chrome 选项卡崩溃,并显示“Aw snap”消息。所以,在边缘地带小心行事,各位。
很多人不知道,但 CSS 网格已经支持这个用例。具有未指定高度的网格具有高度为 1fr 的网格轨道。因此,您可以从 0fr 平滑过渡到 1fr。
我们不是早就用 css 网格解决了这个问题吗?
首先我要指出一个错字,它让我感到困惑(直到我理解了这个错字)。
> […] 但实际上它的计算高度为 auto,除非声明了特定高度。
“计算高度或 auto” 应该是 “计算高度为 auto”。
其次:我认为你对
transition-behavior: allow-discrete;
在display: none;
参与时的解释是错误的。在 Chris 的文章中,从display: none
切换到display: something
的部分被省略了,或者至少我没有看到它,但据我了解,设置它的样式或代码会立即切换该属性,所以我们永远无法看到height
的关闭动画。使用transition-behavior: allow-discrete;
,实际上切换到display: none
只会在任何动画/过渡完成后才生效。(当样式将元素从display: none
更改为display: something
时,它的行为正好相反。)需要
@starting-style
,因为如你所述,具有display: none
的元素最终会以height: auto
的计算样式呈现,无论通过样式将height: 0
属性设置为多少。不直观,但可能是古老的、规范的、已建立的浏览器行为。这意味着我们将尝试从
auto
过渡到auto
,这将什么也不做。因此@starting-style { height: 0; }
。第三:感谢您的文章。
太好了。不用一行 JS 就能为打开/关闭
<details><summary>
块制作 CSS 动画!我也在 Stackoverflow 上写了关于
display
属性过渡的文章:https://stackoverflow.com/a/78304935/104380多年来,我一直使用
max-height: 0;
并将其过渡到max-height: 1000px;
(比内容可能达到的高度更大)来代替使用高度。这样做的效果很好,无需固定高度。但这只是一个权宜之计。我很高兴现在不再需要它了。