以《.css | 动画笔记》最后一个小节为 prompt,ChatGPT 的回答如下:
以《.css | 动画笔记》最后一个小节为 prompt,ChatGPT 的回答如下:
对,这里确实有一点“Radix 魔法”,但它不是魔法,而是一个很经典的 headless component + CSS variable + runtime measurement 技巧。
var(--radix-accordion-content-height) 的本质是:
Radix 在 Content 展开/收起时,先测量真实内容高度,然后把这个高度写入一个 CSS custom property,CSS 动画只负责从
0到这个变量值之间过渡。
为什么不能直接 animate height: auto?
这是关键。
CSS 动画不能直接从:
height: 0;
到:
height: auto;
因为 auto 不是一个具体数值。
例如:
.content {
height: 0;
transition: height .2s;
}
.content.open {
height: auto;
}
浏览器不知道:
0px → ???px
应该怎么插值。
所以传统做法:
element.scrollHeight
拿到真实高度:
height: 237px;
然后动画:
0px → 237px
Radix 做了什么?
大概流程如下:
1. AccordionContent 渲染
你的:
<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>
2. Tailwind 动画读取变量
你的 CSS:
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
实际运行:
height:
0px
↓
237px
所以动画成立。
为什么每个 Accordion 实例高度不同?
因为 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 没有?
因为 HeadlessUI 的设计哲学不同。
Radix:
我提供行为 + 状态 + 必要的测量,你负责样式。
HeadlessUI:
我提供状态,你负责动画。
例如 HeadlessUI Disclosure:
<Disclosure>
{({open}) => (
<>
<DisclosureButton>
Title
</DisclosureButton>
<DisclosurePanel>
Content
</DisclosurePanel>
</>
)}
</Disclosure>
它只告诉你:
open === true
但是:
- 内容高度是多少?
- 怎么测量?
- height 动画怎么做?
它不管。
HeadlessUI 推荐怎么动画?
一般用:
<DisclosurePanel
transition
className="
transition
duration-200
ease-out
data-closed:-translate-y-2
data-closed:opacity-0
"
>
但是注意:
这里动画的是:
opacity
transform
不是:
height
因为 height auto 动画仍然是 CSS 难题。
如果你想在 HeadlessUI 复刻 Radix 效果
有几个方案。
方案 1:自己测量(最接近 Radix)
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 的核心思想。
方案 2:使用 Framer Motion(React 世界更常见)
例如:
<motion.div
initial={{height:0}}
animate={{height:"auto"}}
exit={{height:0}}
>
content
</motion.div>
Framer Motion 内部帮你做:
measure scrollHeight
↓
animate numeric height
↓
restore auto
这是现在 React 项目非常常见的方法。
方案 3:CSS grid hack
现在也有一种纯 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 的“魔法”
实际上是三个技术组合:
Radix Accordion
|
|
v
测量 DOM scrollHeight
|
|
v
写入 CSS variable
--radix-accordion-content-height: xxxpx
|
|
v
Tailwind animation
height:0 → var(...)
Radix 只是把这套复杂逻辑封装起来。
这个设计其实很符合它的定位:
- Radix 管行为和无障碍
- CSS 管视觉
- CSS variable 是两者之间的桥梁
你之前从 shadcn/Radix 换到 HeadlessUI,丢失的其实不是 Accordion 功能,而是 Radix 提供的“动画基础设施”。如果你的博客文章卡片非常依赖这种“展开摘要”的体验,我会认为 Radix Accordion 或者 Framer Motion 会比 HeadlessUI Disclosure 更合适。
本文收录于以下合集: