关于 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 方案。

相关推荐
ashcn20011 小时前
水滴按钮解析
前端·javascript·css
Java陈序员4 小时前
告别手写礼簿!一款开源免费的电子红白喜事礼簿系统!
javascript·css·html
winfredzhang8 小时前
从零构建:基于 Node.js 的全栈视频资料管理系统开发实录
css·node.js·html·音视频·js·收藏,搜索,缩略图
加个鸡腿儿1 天前
经验分享2:SSR 项目中响应式组件的闪动陷阱与修复实践
前端·css·架构
华仔啊1 天前
写 CSS 用 px?这 3 个单位能让页面自动适配屏幕
前端·css
菩提小狗1 天前
Sqli-Labs Less-3 靶场完整解题流程解析-豆包生成
前端·css·less
web小白成长日记1 天前
CSS 作用域隔离实战:React、Vue 与 Styled Components 的三种范式
前端·css·vue.js·react.js
@@小旭2 天前
实现头部Sticky 粘性布局,并且点击菜单滑动到相应位置
前端·javascript·css
Irene19912 天前
CSS 定位属性(relative、absolute、fixed、sticky)与实用技巧总结
css
我的写法有点潮2 天前
推荐几个国外比较流行的UI库(上)
前端·javascript·css