替换12列网格的解决方案(附代码)

138 阅读6分钟

让我们使用CSS grid和flexbox来创建简化的响应式网格系统,并抛弃大量来自重型框架的12列网格系统。

如果你还没有真正研究过网格,或者依赖框架为你考虑Flexbox,这将有助于你加深理解🚀。

纵观整个网络,你会经常看到内容以几种特定的方式布置:

  • 容器的全宽
  • 两个等宽的列
  • 三个等宽的列
  • 四个等宽的列

通常,这是由相当多的实用类设置跨断点的宽度完成的。

在CSS grid和flexbox之间,并考虑到前述的布局,我们可以大大减少响应式网格列的设置。

对于这两种解决方案,我们将只创建两个类,并能够处理从1-4列的内容,响应地平等调整🙌。

注意:这些解决方案对于定义主要的页面布局容器来说效果最好,但我们最后会提出一些建议,以填补其他布局对齐需求的空白。

网格解决方案

Grid擅长网格,正如它的名字所暗示的那样。在这里,"列 "和 "行 "是你使用CSS网格的固有方式,这可以使你的解决方案的定义更加清晰。

特别是以下这些有用的功能:

  • grid-gap - 定义网格项目之间的相等空间,无论是列还是行
  • repeat() - 快速定义每一行或每一列的规则,或一组行或列的数量
  • fr 单位--可用于分配给该列或行的空间的 "分数"
  • minmax() - 定义一个最小和最大的可接受的列宽或行高

.grid-wrap

首先,我们创建一个包装类。这只是为了应用相当于我们的grid-gap 值作为填充,完全是可选的。你可能想要这个,因为grid-gap 属性并没有将间隙间距应用到网格的外部。也许padding已经应用在你的包含元素上了,这个元素可能是body ,或者你可能真的希望你的网格列能够接触到视口的边到边。

$gridGap: 2rem;

.grid-wrap {
  padding: $gridGap;
}

.grid

这就是它--一个可以快速将任何元素变成网格容器的类,它的直接子元素就会变成等宽的、响应式的列。

下面是完整的规则,然后我们将把它分解:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax($minColWidth, 1fr));
  gap: 2rem;

  & + .grid {
    margin-top: $gridGap;
  }
}

首先,我们为我们的内容列定义一个最小宽度。我建议这个值使用rem ,这样它在整个体验中是一致的。如果我们根据em ,它将随着基本元素字体大小的改变而改变。了解更多关于使用单位的工作 >

然后,神奇之处在于我们如何定义grid-template-columns

我们使用repeat 函数来表示我们希望在所有存在的列中应用相同的参数。

然后,我们使用auto-fit ,而不是一个绝对数字,它负责确保列保持等宽,通过拉伸列来填补任何可用的空间。

之后,我们使用minmax() 来设置允许的最小列宽,然后使用1fr 作为最大列宽,确保内容在空间允许的范围内填满该列。

然后,我们添加我们的间隙,以及一个可选的规则,在连续的.grid 容器之间应用相同的值。

这就是全部的解决方案。

缺点

在3列+网格的情况下,虽然它的反应很好,但在某些视口宽度上,你会出现一个 "孤儿 "列。

你可以用媒体查询来克服这个问题,但它们会很脆弱。

如果防止无主列对设计至关重要,你可能想选择flexbox解决方案。

Flexbox解决方案

我们的 Flexbox 解决方案将模仿网格,优先考虑等宽的列。

然而,目前还没有一个完全支持Flexbox的间隙属性(即将推出!),所以我们必须采取一些技巧来达到同样的效果。

.flex-grid-wrap

与网格解决方案的意图相同:

$gridGap: 2rem;

.flex-grid-wrap {
  padding: $gridGap;
}

.flex-grid

固有的flexbox行为将项目放在一排,每个项目随着内容长度的增长而增长,当它增长时,会将下一个项目撞过来。

因此,我们必须添加一些额外的逻辑来创建等宽的行为。

我们用display: flex 来定义规则,然后我们添加一个规则,指示直接的子项使用flex 行为,该行为的评价为:

  • flex-grow: 0 - 防止超过公平共享的空间数量的增长
  • flex-shrink: 1 - 指导元素以相同的速度 "收缩"。
  • flex-basis: 100% - 抵消 指令,仍旧扩大项目以填补可用空间flex-grow
.flex-grid {
  display: flex;

  & > * {
    flex: 0 1 100%;

    &:not(:first-child) {
      margin-left: $gridGap;
    }
  }
}

为了弥补无间隙规则,我们在除第一个项目外的所有项目上定义了margin-left

处理小视口的问题

很好的开始,但这对于小视口来说永远不会崩溃:

current flex column behavior on small viewport

正如一开始所指出的,由于这个网格解决方案旨在用于主要的页面布局容器,我们将引入媒体查询,通过允许flex-wrap: wrap ,插入一个断点,并将我们的保证金 "间隙黑客 "转换为顶部而不是左侧的保证金。

为了确定何时增加包装,基线方案将我们可接受的最小宽度乘以3。这里的逻辑是,一旦3列的单个宽度小于我们可接受的最小宽度,我们就会中断,并将所有东西都改为全宽。根据你可接受的最小值,你可以改变这个规则。

.flex-grid {
  // ...existing styles
  @media (max-width: ($minColWidth * 3)) {
    flex-wrap: wrap;

    & > * {
      margin: 2rem 0 0 !important;
    }
  }

  @media (min-width: ($minColWidth * 3)) {
    & + .flex-grid {
      margin-top: $gridGap;
    }
  }
}

我们还添加了一个min-width 查询,这样我们就可以在较大的视口上有上边距的 "间隙"。如果我们在小视窗上也这样做,我们就会在各组内容之间产生双倍的空白,这可能是一个理想的结果。

缺点

对页面中的子容器应用这个网格可能会导致不理想的断点问题,因为它是一个手动媒体查询,看的是视口宽度而不是容器宽度。

可能的补救措施:不要总是应用max-width 查询,你可以用一个类来应用它。这样就可以在子容器中使用这个基础网格的想法,并减少不理想的结果。

哪个更好?

所提出的解决方案是非常普遍的,但有广泛的应用。

每个方案的意图都是应用于body 的直接子节点,或者深入一层,比如一个main 组件,该组件限制了内容传播的整体max-width ,但仍然与视口同步向下响应。

选择网格

  • 你想利用auto-fit +minmax 的行为,一旦达到最小可接受的宽度,就自动将项目撞到新的一行。
  • 你打算在子容器中使用,因为媒体查询不需要应用断点(你可以通过设置较小的最小宽度,将这个想法扩展到导航条或卡片动作项等组件上)。
  • 你想几乎实现容器查询,因为项目是根据其内容长度来响应的

选择Flexbox

  • 你需要 "网格 "行为的唯一地方是布局主要的页面容器,例如定义卡片的行或创建两栏文本内容
  • 你想防止出现 "孤儿 "列

如果你真的想要一个12列的网格,那么你可以使用这个网格

这就是了--但你要负责按照你的意愿在上面放置项目,这意味着要有更多的自定义CSS规则:)

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 2rem;
}

另外,你也可以只创建一些有针对性的类来更清楚地定义列的期望。请注意,这种类型的用法意味着列将精确地占用等于1/2、或1/3、或1/4的空间的部分。因此,如果你在2cols 网格中只有一列,它仍然只会横跨总宽度的一半,而不会填满可用空间:

.grid {
  display: grid;
  gap: 2rem;

  &--2cols {
    grid-template-columns: repeat(2, 1fr);
  }

  &--3cols {
    grid-template-columns: repeat(3, 1fr);
  }

  &--4cols {
    grid-template-columns: repeat(4, 1fr);
  }
}