如何用纯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获取真实高度才能实现完美过渡。
实现方案
- 通过
useRef获取内容容器的DOM节点,在组件挂载时获取其真实高度,作为展开状态的maxHeight值。 - 折叠时切换到
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
相关产品推荐
相关产品推荐

