CSS Grid 布局的一个坑

上周做后台管理系统,有个列表页需要响应式布局。我想着用 Grid 应该很简单,结果踩了个坑。

问题场景

需求是这样的:大屏三列,平板两列,手机一列。我想当然写了:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

@media (max-width: 768px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .grid {
    grid-template-columns: 1fr;
  }
}

看起来没问题对吧?但是当列表项高度不一致的时候,问题来了——有些卡片会”悬空”,和上面的卡片之间有很大的空隙。

原因

Grid 默认的 align-itemsstretch,但这不会让卡片自动填充垂直方向的空隙。而且 grid-auto-flow 默认是 row,元素按行填充,高度参差不齐时就会出现这种情况。

解决方案

折腾了半天,最后用了 grid-auto-flow: dense,让后面的元素自动往前补位:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
  grid-auto-flow: dense;
}

.card {
  break-inside: avoid;
}

等等,这样写的话 repeat(auto-fill, ...) 配合媒体查询就有点多余了。直接用 minmax 让它自己判断列数就行。

说实话最开始我用了 Masonry 库,后来发现 Grid 就能搞定,感觉有点多余了。但如果你的卡片高度差异特别大,Masonry 还是更稳定一些。

还有个问题

用了 dense 之后,DOM 顺序和视觉顺序可能不一致,如果用户用 Tab 键导航,焦点跳转会很奇怪。无障碍这块儿确实得注意。

后来想了想,干脆把卡片高度统一了,问题从根本上解决。产品设计也觉得这样更整齐。

总结

Grid 很强,但也不是万能的。有些场景传统的 Float 或者 Flex 换行反而更省心。选方案的时候多想想实际需求,别为了用新特性而用。