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

如何创建满足特定等高规则的带滚动条动态高度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>

核心逻辑说明

  1. 固定区域高度计算:先算出父容器中所有非列表元素的总高度,得到列表容器可使用的剩余空间
  2. 内容变化监听:用MutationObserver实时监听列表条目的添加/删除,触发高度调整
  3. 动态高度分配:
    • 当两个列表都能平分空间时,保持Flex平分逻辑
    • 一长一短时,短容器用自然高度,长容器占据剩余空间,同时设置最小高度锁定当前高度,防止后续被挤压
    • 两个都过长时,强制平分可用空间并锁定最小高度,维持等高状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 11:37:01