关于 Grid 布局的一种响应式应用

前言

最近在做一个需求,要在页面上做一个图片墙,要求是展示不同大小的图片,但又要排成一个固定的模板样子。

大概像这样:

一看到这种不规则拼贴的效果,我第一反应就是用 CSS Grid 。Grid 在处理二维布局的时候是真的好用,不光能精确控制行和列,还能用 grid-template-areas 直接把布局语义化。尤其是遇到响应式场景时,Grid 简直就是开发效率和界面一致性的神器。


实现思路

常规的 Grid 写法就是在样式里给每个子元素设置它占几行几列、行高列宽多少。但问题是:如果要搞一堆不同的模板,那每次都要写一堆样式,维护起来挺麻烦。而且要是整个模块需要做缩放(比如响应式适配),光靠设置行列宽高也不好用,就算用 clamp 也还是怪怪的。

像这种情况,直接用 grid-template-areas 会舒服很多。

比如我们把这个布局拆成最小的"方格单元",最后抽象成一个 6 列 × 4 行 的网格。格子之间留固定间隙(比如 0.89rem),要求每个小格子保持正方形,并且能随容器宽度自适应。

模板可以这样定义:

ts 复制代码
const GRID_AREAS_TEMPLATE = {
  ONE: [
    ["A","A","A","B","C","C"],
    ["A","A","A","D","C","C"],
    ["A","A","A","E","F","F"],
    ["G","H","I","J","F","F"],
  ],
};

不同的字母就代表不同的区域,子元素通过 gridArea 对应。这样一来,我们可以提前准备多个模板,按场景动态切换。


React 示例

比如这里我直接写了一个 Demo:

tsx 复制代码
<div
  style={{
    gridTemplateColumns: "repeat(6, 1fr)",
    gridTemplateRows: "repeat(4, 1fr)",
    gridTemplateAreas: template.map(row => `"${row.join(" ")}"`).join(" "),
    gap: "0.89rem",
  }}
>
  {imgsEls.map((el, index) => {
    const area = String.fromCharCode(65 + index); // A,B,C...
    return (
      <div
        key={index}
        style={{ gridArea: area }}
        className="flex items-center justify-center"
      >
        {el}
      </div>
    );
  })}
</div>

打开控制台就能看到 Grid 的区域分布了。

不过问题来了:小格子不是正方形,行高和列宽有 1~2px 的偏差。


为什么会有偏差?

原因其实很简单:

  • 列宽计算时,会减去所有横向 gap,然后平均分给列
  • 行高的 1fr 是"分剩下的空间",没考虑纵向 gap

结果就是算出来有点对不齐,如果展示图片,可能会导致图片模糊。


解决办法:计算容器的正确高度

本质问题就是 Grid 容器的高度没算准。要让每个格子保持正方形比例,我们可以自己用 padding-top 把高度撑出来。

公式大概是这样的:

text 复制代码
容器高度 = 行数 × 每列宽度 + (行数 - 1) × gap
每列宽度 = (100% - (列数 - 1) × gap) / 列数

所以最后写法是:

tsx 复制代码
paddingTop: `calc(${rows} * ((100% - ${columns - 1} * ${gap}) / ${columns}) + ${rows - 1} * ${gap})`

实战代码

tsx 复制代码
const columns = 6;
const rows = 4;
const gap = "0.89rem";
const gridTemplateAreas = GRID_AREAS_TEMPLATE.ONE.map(row => `"${row.join(" ")}"`).join(" ");

return (
  <div
    className="relative w-full"
    style={{
      paddingTop: `calc(${rows} * ((100% - ${columns - 1} * ${gap}) / ${columns}) + ${rows - 1} * ${gap})`,
    }}
  >
    <div
      className="absolute inset-0 grid h-full w-full"
      style={{
        gridTemplateColumns: `repeat(${columns}, 1fr)`,
        gridTemplateRows: `repeat(${rows}, 1fr)`,
        gridTemplateAreas,
        gap,
      }}
    >
      {imgsEls.map((el, index) => {
        const area = String.fromCharCode(65 + index); // A, B, C...
        return (
          <div
            key={index}
            style={{ gridArea: area }}
            className="flex items-center justify-center"
          >
            {el}
          </div>
        );
      })}
    </div>
  </div>
);

这样就能保证:

  • 容器高度和宽度匹配,格子永远是正方形
  • 布局用 grid-template-areas 来控制,清晰直观
  • 想换模板的时候只改配置就行

其他可选方案

除了上面这种 padding-top 算高度的办法,其实 Grid 还有不少响应式写法:

  1. auto-fit / auto-fill 自动列
  2. aspect-ratio 直接控制比例
  3. 媒体查询切列数
  4. grid-auto-rows + minmax 动态行高
  5. 固定 px + auto-fill 保证无偏差

具体写法我就不在这啰嗦了,反正都能解决不同场景的问题。


总结

这次主要踩的坑就是 格子不是正方形,最后靠算高度搞定了。

整个过程下来,我觉得几个要点特别有用:

  1. 模板化 :用 GRID_AREAS_TEMPLATE 来抽象布局,换场景只改配置
  2. 自适应padding-top 算高度,保证格子比例
  3. 多方案结合:Grid 其实提供了很多玩法(auto-fit、aspect-ratio、minmax...),要灵活用

所以无论是做图片墙、仪表盘,还是游戏地图,都能找到适合的 Grid 方案。

相关推荐
有事没事实验室12 小时前
书写腾讯天气遇到的问题
前端·css·html
@HNUSTer13 小时前
基于 HTML、CSS 和 JavaScript 的智能图像锐化系统
开发语言·前端·javascript·css·html
hvinsion15 小时前
零依赖每月工作计划备忘录:高效管理你的每一天
javascript·css·python·开源·html
薛定谔的算法15 小时前
标准盒模型与怪异盒模型:前端布局中的“快递盒子”公摊问题
前端·css·trae
一枚前端小能手1 天前
🔥 z-index明明设了999999还是不生效呢
前端·css
子兮曰2 天前
Flex布局完全指南:20种实战方案带你玩转页面排版
前端·css·flexbox
前端世界2 天前
前端必看:为什么同一段 CSS 在不同浏览器显示不一样?附解决方案和实战代码
前端·css
@HNUSTer2 天前
基于 HTML、CSS 和 JavaScript 的智能图像边缘检测系统
开发语言·前端·javascript·css·html
楼田莉子2 天前
前端学习——CSS
前端·css·学习