You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中details组件CSS展开动画仅首次生效问题排查

解决details/summary组件展开动画仅首次生效的问题

这问题本质是浏览器重排/重绘机制和React DOM更新逻辑的冲突:首次打开时浏览器会自动计算内容高度并触发过渡,但第二次打开时,React的diff算法复用了DOM节点,浏览器没有重新计算高度变化,自然不会触发动画;而打开DevTools会强制浏览器触发重排,所以动画又恢复正常。

可行解决方案一:手动控制高度过渡(推荐)

通过监听details的toggle事件,手动计算内容容器的真实高度,配合强制重排触发动画,同时兼容内容高度动态变化的场景:

import { useRef, useEffect } from 'react';

function CollapsibleComponent() {
  const contentRef = useRef(null);
  const detailsRef = useRef(null);

  useEffect(() => {
    const handleToggle = () => {
      const contentEl = contentRef.current;
      if (!contentEl) return;

      if (detailsRef.current.open) {
        // 展开时:先获取真实高度,设置后触发过渡
        const realHeight = contentEl.scrollHeight;
        contentEl.style.height = `${realHeight}px`;
        // 过渡结束后重置为auto,支持内容动态变化
        const resetHeight = () => {
          contentEl.style.height = 'auto';
          contentEl.removeEventListener('transitionend', resetHeight);
        };
        contentEl.addEventListener('transitionend', resetHeight);
      } else {
        // 收起时:先把高度设为当前真实高度,再过渡到0
        const realHeight = contentEl.scrollHeight;
        contentEl.style.height = `${realHeight}px`;
        // 强制触发重排,让浏览器识别高度变化
        void contentEl.offsetHeight;
        contentEl.style.height = '0';
      }
    };

    detailsRef.current?.addEventListener('toggle', handleToggle);
    return () => {
      detailsRef.current?.removeEventListener('toggle', handleToggle);
    };
  }, []);

  return (
    <details ref={detailsRef}>
      <summary>点击展开/收起</summary>
      <div className="content-wrap" ref={contentRef}>
        {/* 这里放任意高度的内容:文本、图片、组件都可以 */}
        <p>这是一段可变高度的文本内容,可能包含多行或者动态加载的内容...</p>
        <img src="your-image-url.jpg" alt="示例图片" />
        <ul>
          <li>列表项1</li>
          <li>列表项2</li>
          <li>列表项3</li>
        </ul>
      </div>
    </details>
  );
}

对应的CSS:

.content-wrap {
  overflow: hidden;
  transition: height 0.3s ease-in-out;
  height: 0;
}

可行解决方案二:通过key强制React重新渲染容器

如果内容不需要保留状态,可以给内容容器加动态key,让React在每次展开时重新创建DOM节点,确保浏览器计算新的高度:

import { useState, useRef } from 'react';

function CollapsibleComponent() {
  const [isOpen, setIsOpen] = useState(false);
  const detailsRef = useRef(null);

  const handleToggle = () => {
    setIsOpen(!isOpen);
  };

  return (
    <details ref={detailsRef} open={isOpen} onToggle={handleToggle}>
      <summary>点击展开/收起</summary>
      {isOpen && (
        <div className="content-wrap" key={Date.now()}>
          {/* 可变高度内容 */}
        </div>
      )}
    </details>
  );
}

这种方法简单但会重新渲染内容,适合内容无状态、性能要求不高的场景。

关键原理

  • 强制重排:void element.offsetHeight会让浏览器立即计算元素布局,确保高度变化被识别
  • 动态高度计算:scrollHeight能获取元素的真实内容高度,解决height: auto无法过渡的问题

内容的提问来源于stack exchange,提问作者bcondict

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 23:11:29