none → block 也能动起来
元素从 display: none 切到 display: block 时,浏览器没有「上一帧」可插值,transition 默认动不起来。两个新能力解决它:@starting-style 提供「动画开始那一刻的初始样式」,transition-behavior: allow-discrete 让
display 这类离散值也能参与过渡。
1 · 为什么 display 不能「补间」?——先认识 animation type
补间(interpolation)的本质,是在起始值和结束值之间求一个「中间值」:进度走到哪,就取那一处的中间态。opacity: 0 → 1 的一半是 0.5、width: 0 → 100px 的一半是
50px、两个颜色的中点是它们在颜色空间里的混合——这些值都活在一段连续的数值空间里,中间值天然存在、可以算出来。CSS 把这类属性的插值方式叫 by computed value。
但有些属性的取值是一组离散的关键字:display 只能是
,visibility 只能是 visible / hidden,position 只能是
。「半个 none」「block 与 flex 的中点」根本不存在——它们是彼此独立的标签,之间没有数值意义上的中间态。这类属性的插值方式就叫 discrete,这正是 display: none → block 默认动不起来的根本原因。
discrete 的插值规则(MDN):设进度为 p,p < 0.5 用起始值,
用结束值——整段过程只在 50% 处切换一次,中间没有任何过渡。
拖下面的进度条 p 感受差异:上面那条用 left(长度,by computed value)定位,跟着 p 平滑滑动;下面那条用 justify-content(关键字,discrete)对齐,只会在 p 跨过 50% 那一刻从 flex-start 直接跳到 flex-end。
1.1 · 哪些属性是 discrete?为什么?
判据就一条:这个属性的取值之间,存不存在「数值意义上的中间值」。有连续数值空间(长度 / 百分比 / 颜色 / 数值)的能求中点 → by computed value;取值是一组互斥关键字、求不出中点的 → discrete。
| 属性 | animation type | 为什么 |
|---|---|---|
display |
discrete | 决定盒子是否生成、用哪种 formatting context,关键字枚举,无中间态 |
visibility |
discrete* | visible / hidden 两态(*有特例,见下) |
position |
discrete | static / absolute… 是整套定位模型的切换,不能「插一半」 |
justify-content · text-align · flex-direction |
discrete | 对齐 / 排版关键字,标签之间无中点 |
opacity |
by computed value | 0–1 的连续数值,0.5 就是半透明 |
width · margin · gap |
by computed value | 长度 / 百分比,可取任意中间值 |
color · background-color |
by computed value | 在颜色空间里插值出过渡色 |
transform · translate · scale |
by computed value | 数值 / 矩阵插值 |
自定义属性 --x |
默认 discrete | 默认当字符串只能跳变;用 @property 注册类型后转为可插值 |
两个细节:其一,visibility 名义上也是 discrete,但被特别处理——在 visible 与 hidden 之间过渡时整段保持 visible,只在结束那刻才真正隐藏,方便「先淡出、再消失」。其二,discrete 值还不可叠加(not additive),所以它们也不参与 animation-composition 的 add /
accumulate(见 核心属性页的动画合成一节)。
2 · 解决:@starting-style + allow-discrete 让离散属性也能动
| 能力 | 作用 |
|---|---|
@starting-style
|
设置动画 / 过渡开始时的初始样式——解决「没有起始帧可插值」的问题。 |
transition-behavior
|
allow-discrete 让 display 等离散属性也能过渡(否则切换是瞬间的)。 |
厘清一个易混点:上面「50% 跳变」是 discrete 属性在 @keyframes / WAAPI 动画里的通用规则。而在 transition 里 display 被特别处理——变为可见时在过渡开始就立刻切到 block,变为 none 时拖到过渡结束才切,正是为了给
opacity 留出完整的淡入 / 淡出时间。下面的 demo 就是这种用法。
左边靠 transition:display: none → block 用 allow-discrete 过渡,并在 @starting-style 里把起始 opacity 设成 0,于是淡入;隐藏时反向淡出,display 拖到过渡结束才切回 none,出场也不再瞬间消失。右边靠
animation:出现时直接跑一段 from{opacity:0} to{opacity:1} 的 forwards 动画,天然带起始帧,不需要 @starting-style。