前端高度与滚动布局笔记(Grid/Flex + min-height: 0

1. 核心结论(先记住这 6 条)

  1. 1fr 是“分剩余空间”,但在很多 Grid 场景里会被内容最小尺寸反向撑开。
  2. 需要“内部滚动”的布局,常规安全写法是 minmax(0, 1fr)(Grid)+ min-height: 0(子项)。
  3. flex-direction: column 中,需要滚动的那一层通常要写 flex: 1; min-height: 0; overflow: auto;
  4. display: grid 中,需要滚动的行通常写 grid-template-rows: auto minmax(0, 1fr);
  5. Ant Design 组件经常有中间包裹层(spin-container / content-holder),滚动要放到“真实内容层”。
  6. margin(例如 margin-top: -12px)在滚动布局里很容易造成遮挡假象,尽量避免。

2. 1fr 到底是什么

  1. fr = fraction,按比例分配“剩余空间”。
  2. grid-template-rows: auto 1fr 的意思是:第一行按内容,第二行吃剩余。
  3. 问题点:第二行实际最小尺寸常受内容影响,内容太多时会把行高顶大。
  4. 因此做滚动布局时,推荐把 1fr 写成 minmax(0, 1fr),明确允许它缩小到 0。

3. 为什么你“去掉固定高度”仍然会溢出

  1. 你只改了子层,父层仍允许内容撑开。
  2. 高度链路有一层没写 min-height: 0,滚动层就拿不到可压缩空间。
  3. Grid 第二行若是 1fr 而不是 minmax(0,1fr),会被内容反向顶高。
  4. Tabs/Spin 等中间层未约束时,overflow: auto 看起来“失效”。

4. 常见场景标准写法

4.1 Flex 两行(头部固定 + 内容滚动)

.page {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.header {
  flex-shrink: 0;
}

.body {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

4.2 Grid 两行(第一行自适应 + 第二行占满并滚动)

.grid {
  height: 100%;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

.grid-item {
  min-height: 0;
  min-width: 0;
}

.scroll-area {
  height: 100%;
  min-height: 0;
  overflow: auto;
}

4.3 Antd Tabs 容器(非常常见)

.tabs-wrap {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.tabs-wrap .ant-tabs {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.tabs-wrap .ant-tabs-content-holder {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.tabs-wrap .ant-tabs-content {
  height: 100%;
  min-height: 0;
}

.tabs-wrap .ant-tabs-tabpane {
  height: 100%;
  min-height: 0;
}

4.4 Antd List + Spin(你这次的关键点)

.list-shell {
  width: 100%;
  height: 0;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
}

.list-shell .ant-spin-nested-loading {
  width: 100%;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.list-shell .ant-spin-container {
  height: 100%;
  min-height: 0;
  overflow: auto;
}

4.5 Antd Table(只 body 滚动)

.table-wrap {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.table-wrap .ant-table-wrapper,
.table-wrap .ant-spin-nested-loading,
.table-wrap .ant-spin-container,
.table-wrap .ant-table,
.table-wrap .ant-table-container {
  min-height: 0;
}

.table-wrap .ant-table-body {
  overflow: auto;
}

5. 日常推荐模板(可直接套)

.layout-root {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.layout-main {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

.layout-main-item {
  min-height: 0;
  min-width: 0;
}

.layout-scroll {
  height: 100%;
  min-height: 0;
  overflow: auto;
}

6. 常见误区

  1. 误区:只要给子元素 overflow: auto 就能滚。
  2. 事实:父链路不收缩时,子元素不会滚,只会继续撑开。
  3. 误区:1fr 一定等于“固定剩余高度”。
  4. 事实:如果没做 minmax(0,1fr),内容可能反向影响它。
  5. 误区:height: 100% 一定有效。
  6. 事实:祖先没有明确高度时,height: 100% 会失效或不可控。
  7. 误区:负 margin 只是视觉微调。
  8. 事实:在滚动容器中容易造成遮挡和可视区错觉。

7. 排障流程(5 分钟版)

  1. 先定位“你期望滚动的层”是哪一层。
  2. 向上检查每一层是否是 flex/grid 子项。
  3. 这些子项补 min-height: 0(横向问题补 min-width: 0)。
  4. Grid 场景把 1fr 改成 minmax(0,1fr)
  5. 清理负 marginposition 偏移、max-height 冲突。
  6. 再检查滚动是否在真实内容层(Antd 常是 .ant-spin-container / .ant-table-body)。

8. DevTools 快速检查法

  1. 在 Elements 面板选中目标滚动层,看 Computedheightoverflow
  2. 按父链逐层看 displaymin-heightheight
  3. 临时加调试样式:
* { outline: 1px solid rgba(255, 0, 0, 0.15); }
  1. 临时给可疑层加:
min-height: 0 !important;
overflow: hidden !important;
  1. 若问题消失,说明就是该层约束链断了。

9. 经验结论(可背)

  1. 想让内部滚动,先保证“外层可收缩”。
  2. Grid 第二行可滚动,优先 minmax(0, 1fr)
  3. Flex 纵向第二行可滚动,优先 flex:1 + min-height:0 + overflow:auto
  4. Antd 场景多检查中间包装层,不只看你写的组件根节点。