CSS 网格布局和渐进增强

在 2017 年春季,我们第一次看到像网格这样的主要规范几乎同时发布到浏览器中,现在我们在 Firefox、Chrome、Opera、Safari 和 Edge 的公开版本中都支持 CSS 网格布局。但是,虽然常青浏览器意味着我们中的许多人会很快看到大多数用户拥有网格布局支持,但我们也需要处理旧的或不支持的浏览器。在本指南中,我们将介绍各种支持策略。

支持的浏览器

CSS 网格布局在所有现代浏览器中都是无前缀的。本指南中详细介绍的所有属性和值的支持在所有浏览器之间是互操作的。这意味着,如果您在 Firefox 中编写一些网格布局代码,它应该在 Chrome 中以相同的方式工作。这不再是一个实验性规范,您可以在生产环境中安全地使用它。

使用 CSS 网格进行布局安全吗?

是的。与任何前端技术选择一样,是否使用 CSS 网格布局取决于您的网站访问者通常使用的浏览器。

开始在生产环境中使用网格

值得注意的是,您不必以“全有或全无”的方式使用网格。从使用网格来增强设计中的元素开始,这些元素可以使用旧方法显示。使用网格布局覆盖旧方法效果出奇地好,因为网格与这些其他方法的交互方式。

浮动

浮动过去常用于创建多列布局。如果您要支持带有浮动布局的旧代码库,则不会发生冲突。网格项目忽略浮动属性;事实上,网格项目优先。在下面的示例中,我有一个简单的媒体对象。如果未从旧版 CSS 中删除 float,因为容器是一个网格容器,则没有问题。我们可以使用 CSS 网格中实现的对齐属性。

float 不再适用,我可以使用 CSS 盒子对齐属性 align-self 将我的内容对齐到容器的末尾

css
* {
  box-sizing: border-box;
}
img {
  max-width: 100%;
  display: block;
}
.media {
  border: 2px solid #f76707;
  border-radius: 5px;
  background-color: #fff4e6;
  max-width: 400px;
  display: grid;
  grid-template-columns: 1fr 2fr;
  grid-template-areas: "img content";
  margin-bottom: 1em;
}
.media::after {
  content: "";
  display: block;
  clear: both;
}
.media .text {
  padding: 10px;
  align-self: end;
}

/* old code we can't remove */
.media .image {
  float: left;
  width: 150px;
  margin-right: 20px;
}
html
<div class="media">
  <div class="image">
    <img src="https://dummyimage.com/150x150" alt="placeholder" />
  </div>
  <div class="text">
    This is a media object example. I am using floats for older browsers and
    grid for new ones.
  </div>
</div>

下图显示了左侧为不支持浏览器的媒体对象,右侧为支持浏览器的媒体对象

A simple example of overriding a floated layout using grid. Both have the image aligned left. The text is vertically aligned at top in the float example and at the bottom in the grid example.

使用特性查询

上面的例子非常简单,我们可以避免编写对不支持网格的浏览器来说会造成问题的代码,旧版代码对支持网格的浏览器来说也不是问题。然而,情况并不总是如此简单。

一个更复杂的例子

在接下来的示例中,我有一组浮动的卡片。我已经给卡片设置了 width,以便 float 它们。为了在卡片之间创建间隙,我在项目上使用了 margin,然后在容器上使用了负边距。

css
.wrapper ul {
  overflow: hidden;
  margin: 0 -10px;
  padding: 0;
  list-style: none;
}
.wrapper li {
  float: left;
  width: calc(33.333333% - 20px);
  margin: 0 10px 20px 10px;
}
html
<div class="wrapper">
  <ul>
    <li class="card">
      <h2>One</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Two</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Three</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Four</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Five</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Six</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
  </ul>
</div>

这个示例演示了我们在浮动布局中遇到的典型问题:如果向任何一张卡片添加额外的内容,布局就会被破坏。

A floated cards layout demonstrating the problem caused by uneven content height. The top row has 3 cards. The fourth card is floated under the third card. Then a bottom row has contains the fifth and sixth cards. There is a largish empty space under the fourth card.

为了兼容旧浏览器,我在项目上设置了 min-height,并希望我的内容编辑器不会添加太多内容,导致布局混乱!

然后我使用网格来增强布局。我可以将我的 <ul> 变成一个具有三列轨道的网格容器。但是,我分配给列表项本身的宽度仍然适用,现在它使这些项的宽度变成轨道宽度的三分之一。

Six very tall, very narrow grid items with text overflowing on the right. After applying grid to our container, the width of the items is now incorrect as they display at one third of the item width.

如果我将宽度重置为 auto,那么这将停止旧浏览器中的浮动行为。我需要能够为旧浏览器定义宽度,并为支持网格的浏览器删除宽度。感谢 CSS 功能查询,我可以在我的 CSS 中直接执行此操作。

使用功能查询的解决方案

功能查询 如果您曾经使用 媒体查询 创建响应式布局,您会觉得它非常熟悉。我们不是检查 视窗 宽度或浏览器的某些功能,而是使用 @supports 规则来检查对 CSS 属性和值对的支持。在功能查询中,我们可以编写任何需要应用现代布局的 CSS,并删除旧布局所需的任何内容。

css
@supports (display: grid) {
  .wrapper {
    /* do anything for grid supporting browsers here. */
  }
}

功能查询拥有出色的浏览器支持,所有支持更新的网格规范的浏览器也都支持功能查询。您可以使用它们来处理我们在增强后的浮动布局中遇到的问题。

我使用 @supports 规则来检查对 display: grid 的支持。然后,我在 <ul> 上执行我的网格代码,将 <li> 上的宽度和 min-height 设置为 auto。我还删除了边距和负边距,并使用 gap 属性替换间距。这意味着我不会在最后一行的框上获得最终边距。现在布局可以正常工作,即使其中一张卡片中的内容比其他卡片多。

css
.wrapper ul {
  overflow: hidden;
  margin: 0 -10px;
  padding: 0;
  list-style: none;
}
.wrapper li {
  float: left;
  width: calc(33.333333% - 20px);
  margin: 0 10px 20px 10px;
}
@supports (display: grid) {
  .wrapper ul {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    margin: 0;
  }
  .wrapper li {
    width: auto;
    min-height: auto;
    margin: 0;
  }
}
html
<div class="wrapper">
  <ul>
    <li class="card">
      <h2>One</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Two</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Three</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Four</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Five</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Six</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
  </ul>
</div>

覆盖 display 的其他值

由于使用浮动创建项目网格存在问题,我们中的许多人会使用与上面显示的浮动方法不同的方法来布局一组卡片。使用 display: inline-block 是一种替代方法。

我再次可以使用功能查询来覆盖使用 display: inline-block 的布局,而且我仍然不需要覆盖所有内容。设置为 inline-block 的项目会变成网格项目,因此 inline-block 的行为不再适用。我在 inline-block 显示模式下对我的项目使用了 vertical-align 属性,但此属性不适用于网格项目,因此一旦项目变成网格项目,它就会被忽略。

css
.wrapper ul {
  margin: 0 -10px;
  padding: 0;
  list-style: none;
}

.wrapper li {
  display: inline-block;
  vertical-align: top;
  width: calc(33.333333% - 20px);
  margin: 0 10px 20px 10px;
}
@supports (display: grid) {
  .wrapper ul {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    margin: 0;
  }
  .wrapper li {
    width: auto;
    margin: 0;
  }
}
html
<div class="wrapper">
  <ul>
    <li class="card">
      <h2>One</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Two</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Three</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Four</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Five</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
    <li class="card">
      <h2>Six</h2>
      <p>We can use CSS grid to overwrite older methods.</p>
    </li>
  </ul>
</div>

我们再次需要解决项目上的宽度,然后解决我们想要增强的任何其他属性。在本例中,我再次使用了 gap 而不是边距和负边距来创建我的间距。

规范如何定义这些覆盖?

CSS 网格布局规范详细说明了为什么当某些东西变成网格项目时我们可以覆盖某些属性的行为。规范的关键部分是

由于此行为在规范中进行了详细说明,因此您可以放心地在对旧浏览器的支持中使用这些覆盖。这里描述的任何内容都不应该被视为“黑客”。相反,我们正在利用网格规范详细说明了不同布局方法之间的交互这一事实。

其他显示值

当元素的父元素设置为 display: grid 时,它将被块化,如 CSS 显示规范 中所定义。在我们设置为 inline-block 的项目中,这就是为什么 display: inline-block 不再适用的原因。

如果您使用 display: table 作为您的传统布局,设置为 display: table-cell 的项目会生成匿名框。因此,如果您使用 display: table-cell 且没有父元素设置为 display-table,则会围绕任何相邻的单元格创建一个匿名表格包装器,就像您将它们包装在一个设置为 display: table 的 div 或其他元素中一样。如果您有一个项目设置为 display: table-cell,然后在功能查询中将父元素更改为 display: grid,则不会发生此匿名框创建。这意味着您可以覆盖基于 display: table 的布局,而无需额外的匿名框。

浮动元素

正如我们已经看到的那样,float 以及 clear 对网格项目没有影响。因此,您无需显式将项目设置为 float: none

垂直对齐

对齐属性 vertical-align 对网格项目没有影响。在使用 display: inline-blockdisplay: table 的布局中,您可能会使用 vertical-align 属性来执行基本对齐。然后,在您的网格布局中,您拥有更强大的框对齐属性。

多列布局

您还可以使用多列布局作为您的传统浏览器计划,因为 column-* 属性应用于网格容器时不适用。