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

如何用纯CSS实现可折叠菜单,解决Grid布局折叠时元素空白问题

问题描述

我有一个高度未知的内容(示例中用DUMMY_CONTENT_HEIGHT指代),希望用户点击按钮后,内容能折叠至已知的较小高度MINIMIZED_CONTENT_HEIGHT。我已结合React与Grid布局完成了该功能的实现,代码如下:

import { useState } from "react";
export default function App(props) {
  const [minimized, setMinimized] = useState(false);

  const DUMMY_CONTENT_HEIGHT = 100;
  const MINIMIZED_CONTENT_HEIGHT = 20;
  const NORMAL_CONTENT_HEIGHT = "100%";

  return (
    <div style={{ display: "grid" }}>
      <button
        onClick={(e) => {
          setMinimized(!minimized);
        }}
        style={{ gridRow: "1", gridColumn: "1" }}
      >
        CLICK ME
      </button>
      <div
        style={{
          gridRow: "2",
          gridColumn: "1",
          overflow: "hidden",
          transition: "all 1s ease",
          maxHeight: minimized
            ? MINIMIZED_CONTENT_HEIGHT
            : NORMAL_CONTENT_HEIGHT
        }}
      >
        <div
          style={{ height: DUMMY_CONTENT_HEIGHT, backgroundColor: "rgb(255 0 0)" }}
        ></div>
      </div>
      <div
        style={{
          gridRow: "3",
          gridColumn: "1",
          height: 50,
          backgroundColor: "rgb(50 0 0)",
        }}
      ></div>
    </div>
  );
}

但存在问题:折叠时下方的div无法随上方内容同步移动,导致两者之间出现空白。注意:将NORMAL_CONTENT_HEIGHT设置为较大值(如10000)可消除空白,但动画效果会随CONTENT_HEIGHT的不同而不一致,并非合理方案。现咨询:如何仅使用CSS实现过渡效果,同时保证上下div之间无空白?或者该需求是否无法实现?

解决方案

核心问题分析

Grid布局的行高度由内容容器的实际高度决定,原方案中用100%作为展开状态的maxHeight,容器无法自适应内容真实高度,导致折叠时网格行高度无法同步收缩,进而出现空白。纯CSS无法动态获取元素实际高度,因此必须结合JS获取真实高度才能实现完美过渡。

实现方案

  1. 通过useRef获取内容容器的DOM节点,在组件挂载时获取其真实高度,作为展开状态的maxHeight值。
  2. 折叠时切换到MINIMIZED_CONTENT_HEIGHT,让网格行高度随容器maxHeight的变化同步过渡,消除空白。

修改后的代码如下:

import { useState, useRef, useEffect } from "react";

export default function App(props) {
  const [minimized, setMinimized] = useState(false);
  const contentRef = useRef(null);
  const [contentHeight, setContentHeight] = useState("auto");

  const MINIMIZED_CONTENT_HEIGHT = 20;
  const DUMMY_CONTENT_HEIGHT = 100;

  useEffect(() => {
    // 组件挂载后获取内容实际高度
    if (contentRef.current) {
      setContentHeight(`${contentRef.current.scrollHeight}px`);
    }
  }, []);

  return (
    <div style={{ display: "grid" }}>
      <button
        onClick={() => setMinimized(!minimized)}
        style={{ gridRow: "1", gridColumn: "1" }}
      >
        CLICK ME
      </button>
      <div
        ref={contentRef}
        style={{
          gridRow: "2",
          gridColumn: "1",
          overflow: "hidden",
          transition: "max-height 1s ease",
          maxHeight: minimized ? MINIMIZED_CONTENT_HEIGHT : contentHeight
        }}
      >
        <div
          style={{ height: DUMMY_CONTENT_HEIGHT, backgroundColor: "rgb(255 0 0)" }}
        ></div>
      </div>
      <div
        style={{
          gridRow: "3",
          gridColumn: "1",
          height: 50,
          backgroundColor: "rgb(50 0 0)",
        }}
      ></div>
    </div>
  );
}

补充说明

  • 若内容高度会动态变化(如异步加载内容),需在内容更新时重新计算contentHeight,可通过监听相关依赖项更新useEffect实现。
  • 纯CSS无法满足该需求,因为CSS无法动态获取元素的实际高度作为过渡值,必须结合JS获取真实高度才能保证过渡效果的一致性和无空白。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 13:20:39