如何创建满足特定等高规则的带滚动条动态高度Flex容器
实现动态等高且可伸缩的列表容器
需求说明
- 当其中一个列表条目极少,另一个条目多时,条目多的容器占据剩余所有可用空间
- 短列表扩展(增加条目)时,长列表收缩让出空间,直到二者高度相等
- 等高后不再伸缩,保持固定高度
- 反向场景(长列表先出现,短列表扩展)同样适用,始终是长列表从短列表"借用"空间
- 列表条目超过分配空间时,显示垂直滚动条
现有问题
用Flex布局尝试实现时,存在核心问题:当两个容器高度相等后,继续向其中一个添加条目,该容器会持续扩展,导致另一个容器不断收缩,无法维持固定等高状态。
解决方案
由于纯CSS无法直接实现「达到等高后停止伸缩」的逻辑,需要结合JavaScript监听内容变化,动态设置容器的最小高度来锁定等高状态。
修改后的完整代码
<!DOCTYPE html> <html> <title>动态等高列表容器</title> <head> <style> .parent { background: grey; max-height: 90vh; display: flex; flex-direction: column; overflow: hidden; gap: 0; } .ul-container { flex-shrink: 1; overflow-y: auto; background: yellow; opacity: 0.5; } .ul-container .title { background: white; margin: 0; padding: 4px; } ul { margin: 0; padding: 0 0 0 20px; } p { margin: 0; padding: 4px; } </style> </head> <body> <div class="parent"> <p style="background: green;">2323232323</p> <div class="ul-container" id="container1"> <p class="title">Title 1</p> <ul id="list1"> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> </ul> </div> <p style="background: purple;">2323232323</p> <p style="background: blue;">2323232323</p> <div class="ul-container" id="container2"> <p class="title">Title 2</p> <ul id="list2"> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> <li>Item 2</li> <li>Item 1</li> </ul> </div> <p style="background: red;">2323232323</p> </div> <script> const parent = document.querySelector('.parent'); const container1 = document.getElementById('container1'); const container2 = document.getElementById('container2'); const list1 = document.getElementById('list1'); const list2 = document.getElementById('list2'); // 计算父容器中非列表区域的总高度 function getNonListHeight() { let total = 0; Array.from(parent.children).forEach(child => { if (!child.classList.contains('ul-container')) { total += child.offsetHeight; } }); return total; } // 更新容器高度逻辑 function updateContainerHeights() { const nonListHeight = getNonListHeight(); const availableHeight = parent.clientHeight - nonListHeight; // 获取两个列表内容的自然高度(不含滚动条) const contentHeight1 = list1.scrollHeight; const contentHeight2 = list2.scrollHeight; // 计算两个容器的基础高度(标题+内容) const containerContentHeight1 = container1.querySelector('.title').offsetHeight + contentHeight1; const containerContentHeight2 = container2.querySelector('.title').offsetHeight + contentHeight2; // 情况1:两个内容高度都小于等于可用空间的一半,平分空间 if (containerContentHeight1 <= availableHeight / 2 && containerContentHeight2 <= availableHeight / 2) { container1.style.flexGrow = '1'; container2.style.flexGrow = '1'; container1.style.minHeight = 'auto'; container2.style.minHeight = 'auto'; } // 情况2:容器1内容过长,容器2内容较短 else if (containerContentHeight1 > availableHeight / 2 && containerContentHeight2 <= availableHeight / 2) { const shortHeight = containerContentHeight2; const longHeight = availableHeight - shortHeight; container1.style.flexGrow = '0'; container1.style.height = `${longHeight}px`; container2.style.flexGrow = '0'; container2.style.height = `${shortHeight}px`; // 设置最小高度,防止后续被挤压 container1.style.minHeight = `${longHeight}px`; container2.style.minHeight = `${shortHeight}px`; } // 情况3:容器2内容过长,容器1内容较短 else if (containerContentHeight2 > availableHeight / 2 && containerContentHeight1 <= availableHeight / 2) { const shortHeight = containerContentHeight1; const longHeight = availableHeight - shortHeight; container2.style.flexGrow = '0'; container2.style.height = `${longHeight}px`; container1.style.flexGrow = '0'; container1.style.height = `${shortHeight}px`; container1.style.minHeight = `${shortHeight}px`; container2.style.minHeight = `${longHeight}px`; } // 情况4:两个内容高度都超过可用空间的一半,强制平分空间并锁定高度 else { container1.style.flexGrow = '1'; container2.style.flexGrow = '1'; container1.style.minHeight = `${availableHeight / 2}px`; container2.style.minHeight = `${availableHeight / 2}px`; } } // 监听列表内容变化(添加/删除条目) const observer1 = new MutationObserver(updateContainerHeights); observer1.observe(list1, { childList: true, subtree: true }); const observer2 = new MutationObserver(updateContainerHeights); observer2.observe(list2, { childList: true, subtree: true }); // 初始加载和窗口 resize 时更新高度 window.addEventListener('load', updateContainerHeights); window.addEventListener('resize', updateContainerHeights); </script> </body> </html>
核心逻辑说明
- 固定区域高度计算:先算出父容器中所有非列表元素的总高度,得到列表容器可使用的剩余空间
- 内容变化监听:用
MutationObserver实时监听列表条目的添加/删除,触发高度调整 - 动态高度分配:
- 当两个列表都能平分空间时,保持Flex平分逻辑
- 一长一短时,短容器用自然高度,长容器占据剩余空间,同时设置最小高度锁定当前高度,防止后续被挤压
- 两个都过长时,强制平分可用空间并锁定最小高度,维持等高状态
内容的提问来源于stack exchange,提问作者LY Hooi
相关产品推荐
相关产品推荐

