不能直接用 hidden 切换实现平滑动画,因为 hidden 对应 display: none,属硬切换,浏览器跳过渲染,transition 完全失效;必须使用可动画属性如 max-height(需设具体数值、配合 overflow-hidden)并配合 JS 同步 data-open 状态与强制重排来触发动画。

展开收起动画为什么不能直接用 hidden 切换
因为 hidden 是硬切换(display: none),浏览器跳过渲染,CSS 过渡(transition)完全不生效。你看到的是“啪”一下出现或消失,没有中间帧。
真正能过渡的只有那些支持动画的属性:比如 max-height、opacity、transform。其中 max-height 最常用,但要注意它的值必须是具体数字(不能是 max-height: 0 → max-height: none,这样 transition 会失效)。
-
max-height设为一个略大于内容实际高度的固定值(比如max-h-[600px]),再配合overflow-hidden - 避免用
h-0+h-auto,后者无法过渡 - 如果内容高度差异大,设太小会截断,设太大则收起时有明显延迟
用 max-height + transition 实现平滑收展
核心是控制 max-height 的起始和结束值,并确保元素始终有 overflow-hidden。Tailwind 默认不提供 max-h-0 这类工具类,得自己配或用内联 style,但更推荐用 @layer 扩展:
/* tailwind.config.js */
module.exports = {
content: [],
theme: {
extend: {
maxHeight: {
'0': '0',
'12': '3rem',
'24': '6rem',
'60': '15rem',
'96': '24rem',
}
}
}
}然后在 HTML 中这样用:
立即学习“前端免费学习笔记(深入)”;
<div class="overflow-hidden transition-all duration-300 ease-out [&[data-open='false']]:max-h-0 [&[data-open='true']]:max-h-60"> <p>这里是可展开的内容…</p> </div>
-
data-open是控制开关的布尔状态,由 JS 切换 -
ease-out比ease-in-out更自然——收起时减速,展开时加速 - 别漏掉
overflow-hidden,否则内容会撑开容器
JS 控制逻辑怎么写才不卡顿
关键不是“加 class”,而是**同步更新 data-open 属性 + 触发重排(reflow)**,让浏览器知道要重新计算 height。直接 element.classList.toggle('max-h-0') 不可靠,因为 class 切换和样式计算可能异步。
- 先设
data-open="true",再读offsetHeight强制重排,再移除max-h-0类(或设为max-h-60) - 收起时同理:先设
data-open="false",再读offsetHeight,再加max-h-0 - 用
requestAnimationFrame包一层更稳妥,尤其在 React/Vue 中状态更新后 DOM 尚未就绪
简单示例(原生 JS):
function toggle(el) {
const isOpen = el.dataset.open === 'true';
el.dataset.open = isOpen ? 'false' : 'true';
el.offsetHeight; // 强制 reflow
// 此时 transition 才会触发
}移动端点击区域小导致误触怎么办
折叠面板的展开按钮如果只是文字或小图标,iOS 上容易点不中,或触发两次。根本原因是 Safari 对 click 事件的延迟和冒泡处理不一致。
- 给触发按钮加
touch-action: manipulation(Tailwind 用touch-manipulation) - 避免在
div上直接绑onclick,改用button元素(自带 focus 和 click 语义) - 如果用了
aria-expanded,记得同步更新aria-controls和目标 ID,否则 VoiceOver 可能读错状态 - 不要用
pointer-events-none遮罩层盖住按钮——它会拦截 touchstart
动画本身没问题,问题常出在交互链路的细节上:状态没同步、重排没触发、触摸响应被抑制。这些点漏掉一个,动画就“看起来不动”。


















