

























以《.css | 动画笔记》最后一个小节为 prompt,ChatGPT 的回答如下:
对,这里确实有一点“Radix 魔法”,但它不是魔法,而是一个很经典的 headless component + CSS variable + runtime measurement 技巧。
var(--radix-accordion-content-height) 的本质是:
Radix 在 Content 展开/收起时,先测量真实内容高度,然后把这个高度写入一个 CSS custom property,CSS 动画只负责从
0到这个变量值之间过渡。
这是关键。
CSS 动画不能直接从:
height: 0;
到:
height: auto;
因为 auto 不是一个具体数值。
例如:
.content {
height: 0;
transition: height .2s;
}
.content.open {
height: auto;
}
浏览器不知道:
0px → ???px
应该怎么插值。
所以传统做法:
element.scrollHeight
拿到真实高度:
height: 237px;
然后动画:
0px → 237px
大概流程如下:
你的:
<AccordionPrimitive.Content>
实际上是 Radix 的:
@radix-ui/react-accordion
里面的 Content。
它内部会有类似逻辑:
const height = contentRef.current.scrollHeight
content.style.setProperty(
"--radix-accordion-content-height",
`${height}px`
)
于是 DOM 变成:
<div
data-state="open"
style="
--radix-accordion-content-height: 237px;
"
>
...
</div>
你的 CSS:
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
实际运行:
height:
0px
↓
237px
所以动画成立。
因为 CSS variable 是写在 DOM 节点上的。
例如:
文章 A:
<div
style="
--radix-accordion-content-height:120px
"
>
摘要 A
</div>
文章 B:
<div
style="
--radix-accordion-content-height:350px
"
>
摘要 B
</div>
CSS:
height: var(--radix-accordion-content-height)
读取的是最近祖先作用域中的变量。
CSS variable 是继承机制:
document
|
article A
|
content
--height:120px
document
|
article B
|
content
--height:350px
互不影响。
因为 HeadlessUI 的设计哲学不同。
Radix:
我提供行为 + 状态 + 必要的测量,你负责样式。
HeadlessUI:
我提供状态,你负责动画。
例如 HeadlessUI Disclosure:
<Disclosure>
{({open}) => (
<>
<DisclosureButton>
Title
</DisclosureButton>
<DisclosurePanel>
Content
</DisclosurePanel>
</>
)}
</Disclosure>
它只告诉你:
open === true
但是:
它不管。
一般用:
<DisclosurePanel
transition
className="
transition
duration-200
ease-out
data-closed:-translate-y-2
data-closed:opacity-0
"
>
但是注意:
这里动画的是:
opacity
transform
不是:
height
因为 height auto 动画仍然是 CSS 难题。
有几个方案。
React:
const ref = useRef(null)
useEffect(()=>{
if(ref.current){
ref.current.style.setProperty(
"--content-height",
`${ref.current.scrollHeight}px`
)
}
},[])
然后:
.panel {
overflow:hidden;
animation:
accordion-down .2s ease-out;
}
@keyframes accordion-down {
from {
height:0;
}
to {
height:var(--content-height);
}
}
这就是 Radix 的核心思想。
例如:
<motion.div
initial={{height:0}}
animate={{height:"auto"}}
exit={{height:0}}
>
content
</motion.div>
Framer Motion 内部帮你做:
measure scrollHeight
↓
animate numeric height
↓
restore auto
这是现在 React 项目非常常见的方法。
现在也有一种纯 CSS 技巧:
.panel {
display:grid;
grid-template-rows:0fr;
transition:grid-template-rows .2s;
}
.panel.open {
grid-template-rows:1fr;
}
.inner {
overflow:hidden;
}
React:
<div className="panel">
<div className="inner">
summary
</div>
</div>
利用 grid 的:
0fr → 1fr
绕开:
height:auto
限制。
这个最近很流行。
实际上是三个技术组合:
Radix Accordion
|
|
v
测量 DOM scrollHeight
|
|
v
写入 CSS variable
--radix-accordion-content-height: xxxpx
|
|
v
Tailwind animation
height:0 → var(...)
Radix 只是把这套复杂逻辑封装起来。
这个设计其实很符合它的定位:
你之前从 shadcn/Radix 换到 HeadlessUI,丢失的其实不是 Accordion 功能,而是 Radix 提供的“动画基础设施”。如果你的博客文章卡片非常依赖这种“展开摘要”的体验,我会认为 Radix Accordion 或者 Framer Motion 会比 HeadlessUI Disclosure 更合适。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。