惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Y
Y Combinator Blog
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
I
InfoQ
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园 - 司徒正美
L
LangChain Blog

阿掖山·博客

.doc | 赢家通吃,赢家出局 | 阿掖山·博客 .css | 动画笔记 | 阿掖山·博客 重写.bio | Gibson Assembly 技术组装 DNA 分子 | 阿掖山·博客 涌现(emergence) | Tiki Taka 和德国足协的双向奔赴 | 阿掖山·博客 .bio | 分子生物学中的几种“子” | 阿掖山·博客 动力学[∞] | 从化学反应到基因调控 | 阿掖山·博客 .git | 基于工作流的 Git 学习笔记 | 阿掖山·博客 .bio | Gibson Assembly 技术组装 DNA | 阿掖山·博客 动力学[8] | 分岔 Bifurcation | 阿掖山·博客 动力学[7] | Criticality, 生物系统处于临界状态吗 | 阿掖山·博客 文革史笔记-2.4.3 | 罗瑞卿遭受身心的摧残 | 阿掖山·博客 .tex | 生物系统处于临界状态吗:最大熵原则 | 阿掖山·博客
ChatGPT | Radix Accordion 开合的动画效果 | 阿掖山·博客
阿掖山,一个博客 · 2026-07-15 · via 阿掖山·博客

《.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 更合适。