平滑手风琴

面板展开到内容的自然高度后又能收回,无需固定 max-height,箭头也随之转动。

  • CSS
  • 布局
  • 切换
  • 收录于

不用。大多数动效都是纯 CSS,GSAP 只在编排时间线时有帮助。

会。每个演示在该设置下都会关闭或减弱动作。

可以。粘贴到任意 AI 编程工具里即可重建这个效果。

AI 提示词

用 React 和 CSS 制作一个能动画到内容自然高度的 FAQ 手风琴。每一项有一个带 aria-expanded 和 aria-controls 的标题按钮,以及一个面板区域。面板设为 grid,grid-template-rows: 0fr,内部 div 设置 overflow: hidden 和 min-height: 0;展开时改为 grid-template-rows: 1fr。grid-template-rows 过渡 320ms,缓动 cubic-bezier(0.16, 1, 0.3, 1),内部内容随之淡入。展开时箭头以同样的节奏旋转 180deg。同一时间只允许展开一项。在 prefers-reduced-motion 下直接展开和收起。问题为“一定要用 GSAP 吗?”“会照顾减少动态效果的设置吗?”“可以复制提示词吗?”。

源代码

Demo.tsx
'use client'

import { useId, useState } from 'react'
import s from './demo.module.css'

export default function Accordion({ labels }: { labels: Record<string, string> }) {
  const [open, setOpen] = useState(0)
  const id = useId()
  const items = [1, 2, 3].map((n) => ({ q: labels[`q${n}`], a: labels[`a${n}`] }))

  return (
    <div className={s.list}>
      {items.map(({ q, a }, i) => (
        <div key={q} className={s.item} data-open={open === i || undefined}>
          <h3 className={s.heading}>
            <button
              type="button"
              className={s.trigger}
              aria-expanded={open === i}
              aria-controls={`${id}-${i}`}
              onClick={() => setOpen(open === i ? -1 : i)}
            >
              {q}
              <svg className={s.chevron} viewBox="0 0 24 24" aria-hidden="true">
                <path d="M6 9l6 6 6-6" />
              </svg>
            </button>
          </h3>
          <div id={`${id}-${i}`} role="region" className={s.panel}>
            <div className={s.inner}>
              <p className={s.answer}>{a}</p>
            </div>
          </div>
        </div>
      ))}
    </div>
  )
}
demo.module.css
.list {
  width: min(320px, 100%);
  border-top: 1px solid var(--stage-rule);
}

.item { border-bottom: 1px solid var(--stage-rule); }

.heading { margin: 0; }

.trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 11px 2px;
  border: 0;
  background: transparent;
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1.3 var(--font-body);
  text-align: left;
  cursor: pointer;
}

.trigger:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; border-radius: 4px; }

.chevron {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--stage-muted);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 320ms var(--ease-out);
}

.item[data-open] .chevron { transform: rotate(180deg); stroke: var(--stage-accent); }

.panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 320ms var(--ease-out);
}

.item[data-open] .panel { grid-template-rows: 1fr; }

.inner {
  min-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 240ms var(--ease-out);
}

.item[data-open] .inner { opacity: 1; transition-delay: 80ms; }

.answer {
  margin: 0;
  padding: 0 2px 12px;
  font: 400 var(--text-sm) / 1.5 var(--font-body);
  color: var(--stage-muted);
}

@media (prefers-reduced-motion: reduce) {
  .panel, .inner, .chevron { transition: none; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。